в

Kazan Dev Alliance

Казанское Сообщество Разработчиков Программного Обеспечения

Персональный блог Александра Шера

Professional blog for .Net programmers. Main topics: Microsoft ASP.NET AJAX, WPF/E, OOP, Refactoring

May 2007 - Posts

  • Когда Strategy - слишком много

    Так получилось, что за последний месяц меня несколько раз спрашивали, как бы упростить сложную конструкцию из условных операторов. Все разы я отсылал людей к паттерну Strategy, но в ответ получал: "это слишком сложно". Тут я задумался - люди вроде не новички, может я чего не то говорю? Залез в собственный код и обнаружил, что в чистом виде Strategy практически никогда не использую, а делаю некоторое его упрощение.

    Итак, как же упростить условную логику и не прибегать к созданию полноценной стратегии?

    Вместо вымышленных абстрактных примеров возьму "максимально приближенный к реальности". Предположим, что у нас есть набор стилей для некоторого ascx контрола. Стиль может меняться в зависимости от прав пользователя, его личных настроек и т.п. - сейчас это не принципиально. Важно то, что существует несколько параметров, которые влияют на выбор стиля. 

    Для выполнения задачи было создано несколько классов, реализующих некий интерфейс, а для получения нужного типа был создан вот такой метод:

    [code language="C#"]

    public interface IComplexControlStyle {
        // Некоторый набор методов
    }

    public IComplexControlStyle GetStyle(bool animated, bool editable, bool checkFormat) {
        if (editable) {
            if (animated) {
                if (checkFormat) {
                    return new EditableAnimatedFormattedStyle();
                } else {
                    return new EditableAnimatedUnformattedStyle();
                }
            } else {
                if (checkFormat) {
                    return new EditableStaticFormattedStyle();
                } else {
                    return new EditableStaticUnformattedStyle();
                }
            }
        } else {
            if (checkFormat) {
                if (animated) {
                    return new LockedAnimatedFormattedStyle ();
                } else {
                    return new LockedStaticFormattedStyle();
                }
            } else {
                return new LockedStaticUnformattedStyle();
            }
        }
    }

    [/code]

    Чтобы не было вопроса "а нельзя ли задать то всё по отдельности?", дам небольшое пояснение:

    • EditableAnimatedFormattedStyle - текстовые поля посвечиваются (различными цветами) при наведении и фокусе, а также при наличии ошибок (при наведение на поля с ошибками и без ошибок цвета подсветки разные)
    • EditableAnimatedUnformattedStyle - текстовые поля посвечиваются (различными цветами) при наведении и фокусе
    • EditableStaticFormattedStyle - рядом с текстовыми полями с ошибками появляется красный треугольник
    • EditableStaticUnformattedStyle - никаких опозновательных знаков для текстовых полей
    • LockedAnimatedFormattedStyle - вместо текстовых полей рисуется текст, неправильно отформатированный текст помечен мерцающими рамками
    • LockedStaticFormattedStyle - вместо текстовых полей рисуется текст, неправильно отформатированный текст помечен красным шрифтом
    • LockedStaticUnformattedStyle - вместо текстовых полей рисуется текст без всяких пометок

    Как видим, все параметры достаточно тесно друг с другом связаны. Пытаться разделить их - себе дороже. C другой стороны, код явно выглядит не слишком удобочитаемым, в нем при тех или иных изменениях легко допустить ошибку. Кроме того, если добавится ещё один параметр, то станет совсем неприятно.

    Тем не менее, паттерн Strategy в чистом виде применять не хочется - слишком много придется вносить изменений. Поэтому мы поступим проще.

    1. Напишем маленький метод, переводящий набор булевских значений в битовую маску. Собственно, такой метод много где бывает полезен, поэтому я его к рефакторингу не отношу (у меня он, как правило, есть в каждом новом проекте, притом в 3-х версиях - на C#, JavaScript и SQL):

      [code language="C#"]

      public static int GetMask(params bool[] bits) {
          if (bits.Length > 32) {
              throw new OverflowException();
          }
          int mask = 0;
          for (int j = 0; j < bits.Length; j++) {
              if (bits[j]) {
                  mask += (1 << j);
              }
          }
          return mask;
      }

      [/code]

    2. Объявим несколько вспомогательный констант и сделаем метод для получения фабрик экземпяров класса, а также делегат для него:

      [code language="C#"]

      private const bool Editable = true, Animated = true, CheckFormat = true;
      private const bool Locked = false, Static = false, NoFormatCheck = false;

      private delegate IComplexControlStyle GetStyleDelegate();

      private static GetStyleDelegate GetStyleFactory<T>() where T: IComplexControlStyle, new() {
          return delegate { return new T(); };
      }

      [/code]

    3. Создадим словарь, ставящий в соответствие набору параметров нужный класс стиля:

      [code language="C#"]

      Dictionary<int, GetStyleDelegate> styles = new Dictionary<int, GetStyleDelegate>();
      styles[GetMask(Editable, Animated, CheckFormat)]   = GetStyleFactory<EditableAnimatedFormattedStyle>();
      styles[GetMask(Editable, Animated, NoFormatCheck)] = GetStyleFactory<EditableAnimatedUnformattedStyle>();
      styles[GetMask(Editable, Static, CheckFormat)]     = GetStyleFactory<EditableStaticFormattedStyle>();
      styles[GetMask(Editable, Static, NoFormatCheck )]  = GetStyleFactory<EditableStaticUnformattedStyle>();
      styles[GetMask(Locked, Animated, CheckFormat)]     = GetStyleFactory<LockedAnimatedFormattedStyle>();
      styles[GetMask(Locked, Animated, NoFormatCheck )]  = GetStyleFactory<LockedAnimatedFormattedStyle>();
      styles[GetMask(Locked, Static, CheckFormat)]       = GetStyleFactory<LockedStaticFormattedStyle>();
      styles[GetMask(Locked, Static, NoFormatCheck )]    = GetStyleFactory<LockedStaticUnformattedStyle>();

      [/code]

    4. Меняем исходный метод:

      [code language="C#"]

      public IComplexControlStyle GetStyle(bool animated, bool editable, bool checkFormat) {
          return styles[GetMask(animated, editable, checkFormat)]();
      }

      [/code]

    Вот, собственно, и всё. Единственный серьезный минус подхода в том, что приходится задавать вручную абсолютно все варианты. Лично я знаю 2 способа (один совсем простой, второй посложнее, но открывает новые возможности) как от этой необходимости избавиться. Желающие могут предложить свои в комментариях.

    P.S. Все выше изложенное неплохо абстрагируется в отдельный класс, который затем можно многократно использовать в своем приложении для упрощения условных конструкций.

© 2007 Kazan Developers Community and Post`s Authors