Есть ли шаблон для добавления «вариантов» в класс? - PullRequest
5 голосов
/ 18 октября 2008

У меня есть класс, для которого я хочу разрешить несколько (~ 20+) вариантов конфигурации. Каждый параметр включает или отключает часть функциональности или иным образом изменяет операции. Чтобы облегчить это, я кодировал отдельный класс параметров со значениями по умолчанию. Однако мне пришлось засорять мой код защитными условиями, чтобы определить, как должны вести себя методы. Я почти закончил, но теперь, кажется, код пахнет.

Есть ли предпочтительный метод / шаблон для реализации такого класса?

РЕДАКТИРОВАТЬ: Более конкретно, я работаю над классом синтаксического анализа. Каждый параметр настраивает взаимоисключающие части основного алгоритма синтаксического анализа. Например, в моем классе есть несколько областей, которые выглядят следующим образом:

 if (this.Option.UseIdAttribute)
      attributeIDs = new Hashtable();
 else
      attributeIDs = null;


    public Element GetElementById(string id)
    {
        if (string.IsNullOrEmpty (id))
            throw new ArgumentNullException("id");

        if (attributeIDs == null)
            throw new Exception(ExceptionUseIdAttributeFalse);

        return attributeIDs[id.ToLower()] as Element;
    }

Ответы [ 7 ]

5 голосов
/ 18 октября 2008

Как насчет шаблона Decorator ? Он предназначен для динамического добавления поведения в класс.

2 голосов
/ 18 октября 2008

Для опций, которые включают / выключают функциональность, я думаю, что Decorator - это то, что нужно, как говорит @Thomas Owens. Я немного больше обеспокоен опциями, которые меняют функции. Декоратор много не работает, если эти операции не могут быть связаны. Например, если ваш код выглядит так:

public void ActionABC()
{
   if (options.DoA)
   {
      A();
   }

   if (options.DoB)
   {
      B();
   }

   if (options.DoC)
   {
      C();
   }
}

public void ActionCAB()
{
   if (options.DoC)
   {
      C();
   }

   if (options.DoA)
   {
      A();
   }

   if (options.DoB)
   {
      B();
   }
}

Это было бы трудно решить с помощью Decorator, поскольку порядок композиции для каждого метода действия различается.

С 20+ опциями, при условии, что они включены / выключены, у вас есть более 400 различных возможных комбинаций. Я подозреваю, что не все из этих комбинаций одинаково вероятны. Для вещей, которые вы не можете обработать с помощью Decorator, вы можете подумать о режимах работы, которые соответствуют комбинациям настроек. Поддержка только тех режимов, которые наиболее ожидаемы. Если количество режимов невелико, вы можете обработать это с помощью подклассов, а затем использовать декораторы, чтобы добавить функциональность в подкласс, представляющий режим, выбранный пользователем. Вы можете использовать Factory, чтобы выбрать и построить подходящий класс на основе конфигурации.

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

1 голос
/ 18 октября 2008

Как насчет этого?

IdMap elementsById = (options.useIdAttribute) ? new IdMapImpl() : new NullIdMap();

public Element getElementById(final string id) {
    return elementsById.get(id);
}

На основании следующих типов:

interface IdMap {
    Element get(String id);
}

class NullIdMap implements IdMap {
    public Element get(final String id) {
        throw new Exception(/* Error message */);
    }
}

class IdMapImpl implements IdMap {
    Map<String, Element> elements = new HashMap<String, Element>();

    public Element get(final String id) {
        rejectEmpty(id);
        return elements.get(id.toLowerCase());
    }
}

Здесь мы используем шаблон NullObject для обработки особого случая, когда useIdAttribute отключен. Конечно, есть компромисс - сам класс синтаксического анализатора является более выразительным, хотя теперь существует 4 типа, а не 1. Этот рефакторинг может показаться излишним только для метода get, но когда вы добавляете метод 'put ()' и т. д. Преимущество локализации логики «особого случая» в одном классе (NullIdMap).

[rejectEmpty - это вспомогательный метод, который выдает исключение, если передана пустая строка.]

1 голос
/ 18 октября 2008

Для этой цели действительно существует другая модель, называемая моделью строителя. Обычно это полезно, когда у вас есть класс, но каждый параметр «конфигурации» можно сделать необязательным, возможно, только в некоторых комбинациях. (http://en.wikipedia.org/wiki/Builder_pattern, но это точно не описывает мой сценарий, хотя)

Вы создаете два класса - класс, который вы хотите построить, и конструктор. Класс builder - это класс, который заботится о том, какие комбинации опций являются необязательными, какие комбинации имеют смысл и т. Д.

Например, вы хотите представить салат - но только некоторые ингредиенты «имеют приятный вкус», поэтому должны быть сделаны только те.

Class Salad {
   private Veggie v;
   private Egg e;
   private Meat m;
   // etc etc, lots of properties
   //constructor like this is nice
   Salad(SaladBuilder builder) {
      //query the builder to actually build the salad object. 
      //getVeggie() will either return the supplied value, 
      //or a default if none exists. 
      this.v = builder.getVeggie(); 
      //rest of code omitted
   }

   //otherwise this constructor is fine, but needs a builder.build() method
   Salad(Veggie v, Meat m, Egg e) { //code omitted
   }
}

class SaladBuilder {
   //some default, or left to null depending on what is needed
   private Veggie v = SOME_DEFAULT_VEGGIE;
   private Egg e; 
   private Meat m;
   // etc etc, lots of properties.

   //similar functions for each ingredient, 
   //or combination of ingredients that only make sense together 
   public SaladBuilder addIngredient(Meat m) {
      this.m = m;
      return this;
   }

   public SaladBuilder addIngredient(Veggie v) {
      this.v = v;
      return this;
   }

   public Salad build(){
      // essentially, creates the salad object, but make sure optionals
      // are taken care of here.
      return new Salad(getBeggie(), getMeat(), getEgg());
   }
}

пример использования

Salad s = new SaladBuilder().addIngredient(v).addIngredient(m).build();
0 голосов
/ 21 октября 2008

Здесь может быть полезно использовать что-то вроде стратегии или шаблона политики. Это хороший способ для инкапсуляции или замены различных реализаций алгоритма, основанных на таких вещах, как конфигурация или (не) существование некоторых конкретных данных во время выполнения.

В вашем сценарии, если ваш класс синтаксического анализа обычно имеет те же типы поведения (что-то внутри, что-то вне), но внутренние алгоритмы меняются, это то, на чем вы могли бы основывать реализацию.

http://en.wikipedia.org/wiki/Strategy_pattern

0 голосов
/ 18 октября 2008

Как вы намереваетесь применять правила для опций, которые являются взаимозависимыми? Если вы можете динамически добавлять параметры, вам может потребоваться иметь несколько вызывающих, если вы используете шаблон команды, или вам может потребоваться реализовать оператор case для определения того, какие команды вам нужно выполнить.

0 голосов
/ 18 октября 2008

Я имею в виду шаблон Command .

Это будет выглядеть примерно так:

 public class MyClass {

  interface Command {
    void execute(int a);
  }

  static class DoThisSimpleCommand implements Command {
    void execute(int a) {
      // do this simple
    }
  }

  static class DoThisAnotherWayCommand implements Command {
    void execute(int a) {
      // do this another way
    }
  }

  private Command doThisCommand;

  MyClass() {
    initDoThisCommand();
  }

  private void initDoThisCommand() {
    if (config.getDoThisMethod().equals("simple")) {
      doThisCommand = new DoThisSimpleCommand();
    } else {
      doThisCommand = new DoThisAnotherWayCommand();
    }
  }

  void doThis(int a) {
    doThisCommand.execute(a);
  }
}

Другими словами; вы делегируете ответственность реализации, которую вы создаете в процессе создания. Теперь ваш doThis просто делегат, а реальная функциональность просто убрана в своем собственном классе.

...