Имеет ли этот шаблон стратегии Java избыточный класс Context? - PullRequest
14 голосов
/ 06 января 2010

Следующий пример кода представляет собой реализацию шаблона стратегии , скопированного из Википедии . Мой полный вопрос следует за этим ...

Вики main метод:

//StrategyExample test application

class StrategyExample {

    public static void main(String[] args) {

        Context context;

        // Three contexts following different strategies
        context = new Context(new ConcreteStrategyAdd());
        int resultA = context.executeStrategy(3,4);

        context = new Context(new ConcreteStrategySubtract());
        int resultB = context.executeStrategy(3,4);

        context = new Context(new ConcreteStrategyMultiply());
        int resultC = context.executeStrategy(3,4);

    }

}

Части выкройки:

// The classes that implement a concrete strategy should implement this

// The context class uses this to call the concrete strategy
interface Strategy {

    int execute(int a, int b);

}

// Implements the algorithm using the strategy interface
class ConcreteStrategyAdd implements Strategy {

    public int execute(int a, int b) {
        System.out.println("Called ConcreteStrategyA's execute()");
        return a + b;  // Do an addition with a and b
    }

}

class ConcreteStrategySubtract implements Strategy {

    public int execute(int a, int b) {
        System.out.println("Called ConcreteStrategyB's execute()");
        return a - b;  // Do a subtraction with a and b
    }

}

class ConcreteStrategyMultiply implements Strategy {

    public int execute(int a, int b) {
        System.out.println("Called ConcreteStrategyC's execute()");
        return a  * b;   // Do a multiplication with a and b
    }

}

// Configured with a ConcreteStrategy object and maintains a reference to a Strategy object
class Context {

    private Strategy strategy;

    // Constructor
    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public int executeStrategy(int a, int b) {
        return strategy.execute(a, b);
    }

}

Учитывая конкретно приведенный выше пример, является ли класс Context избыточным?

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

(( Редактировать: В этом простом сценарии, когда я опускаю класс Context, сделаю ли я будущую ошибку? ))

public static void main(String[] args) {

    IStrategy strategy;

    // Three strategies
    strategy = new ConcreteStrategyAdd();
    int resultA = strategy.executeStrategy(3,4);

    strategy = new ConcreteStrategySubtract();
    int resultB = strategy.executeStrategy(3,4);

    strategy = new ConcreteStrategyMultiply();
    int resultC = strategy.executeStrategy(3,4);

}

Сводное обновление

Перечисление в точечной форме того, что было обнаружено посредством ответов и комментариев:

  • Контекст позволяет варьировать способ использования составной стратегии (например, время ее вызова). Различные контексты могут выполнять различную внутреннюю работу до и после вызова данной стратегии.
  • Контекстом является «черный ящик» высокого уровня. Может измениться логика контекста, также может измениться составная стратегия (или другая используемая), не нарушая клиента, потому что клиент понимает только то, как вызывать контекст.
  • Даже несмотря на то, что я создал альтернативную реализацию примера кода Википедии, оставив контекст, и хотя он работал так же, как и оригинал, вся ситуация была упрощена (в обоих случаях) и мои изменения фактически означали: больше не шаблон стратегии, 2. Я скучаю по преимуществам духа шаблона стратегии, которые упомянуты здесь.
  • Моя альтернативная реализация использовала основной метод, такой как Context, поэтому я мог бы также сохранить Context, если эффективно имитирую его. Создав шаблон нечистой Стратегии, возникла путаница. Мне не нужно было изобретать велосипед или пытаться быть умнее (в данном случае).

Если какие-либо другие пункты были бы полезны, или если это требует исправления, оставьте комментарий, и я изменю список соответствующим образом.

Ответы [ 4 ]

16 голосов
/ 06 января 2010

Как следует из названия, Context - это то, что инкапсулирует точку, в которой выполняется стратегия. Без этого у вас просто есть голый Strategy, и вызывающий класс теперь берет на себя дополнительную ответственность: знать, когда вызывать сам Strategy. Ваш пример, возможно, немного слишком прост, и в этом конкретном случае я бы сказал, что Context не слишком вас заводит.

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

public class LoadingDock {   // Context.
  private LoadStrategy ls;   // Strategy.

  public void setLoadStrategy(LoadStrategy ls) { ... }

  // Clients of LoadingDock use this method to do the relevant work, rather
  // than taking the responsibility of invoking the Strategy themselves.
  public void shipItems(List<ShippingItem> l) {
    // verify each item is properly packaged     \
    // ...                                        |  This code is complex and shouldn't be
    // verify all addresses are correct           |  subsumed into consumers of LoadingDock.
    // ...                                        |  Using a Context here is a win because
    // load containers onto available vehicle     |  now clients don't need to know how a
    Vehicle v = VehiclePool.fetch();        //    |  LoadingDock works or when to use a
    ls.load(v, l);                          //   /   LoadStrategy.
  }
}

Обратите внимание, что Strategy никогда не будет вызываться напрямую с внешнего клиента. Только shipItems использует стратегию, а подробности последующих шагов - это черный ящик. Это позволяет Context настроить способ использования стратегии без ущерба для клиентов. Например, шаги могут быть полностью переупорядочены или скорректированы (или полностью удалены) для достижения целей производительности или других целей, но для клиента внешний интерфейс shipItems() выглядит точно так же.

Также обратите внимание, что наш пример Context, LoadingDock, может изменить свой LoadStrategy в любое время в зависимости от его внутреннего состояния. Например, если док становится слишком полным, возможно, он переключится на более агрессивный механизм планирования, который быстрее доставляет ящики с дока в грузовики, жертвуя при этом некоторой эффективностью (возможно, грузовики загружаются не так эффективно, они могли бы быть).

4 голосов
/ 06 января 2010

Это лучший пример того, как настоящий класс "Context" может выглядеть в этом сценарии:

class Accumulator {
    private Strategy strategy; 

    public Accumulator(Strategy strategy) { 
        this.strategy = strategy; 
    } 

    public int accumulate(List<Integer> values) { 
        int result = values.get(0);
        for (int i = 1; i < values.size(); i++) {
           result = strategy.execute(result, values.get(i));
        }
        return result;
    } 
}

РЕДАКТИРОВАТЬ: Исправлена ​​опечатка в конструкторе

3 голосов
/ 06 января 2010

Может быть, для этого вымышленного примера, но тогда я бы не назвал это стратегией ne plus plus.

Класс Context демонстрирует, как вы можете дать классу другое поведение, просто передав новую конкретную реализацию интерфейса. Так как класс знает только интерфейс, ничего не должно измениться. В этом-то и дело. Не воспринимайте остальную часть примера слишком буквально.

То, как вы его закодировали, будет работать, но дело в том, что вы выбросили это в основной метод. Это не будет так, как вы обычно используете стратегию. Вы будете делать это в классе, и Context является простым примером этого.

0 голосов
/ 03 июня 2016

Context не будет избыточным в шаблоне Strategy, и это полезно в следующих сценариях:

  1. Код для вызова определенного Strategy распространяется по нескольким классам без вызова Context. В будущем, если вам придется изменить или изменить интерфейс Strategy, это станет сложной задачей.
  2. Предположим, что некоторые данные необходимо заполнить перед вызовом конкретной Стратегии. Здесь лучше всего подходит контекст, предоставляя дополнительную информацию и вызывая стратегический метод конкретной стратегии.

    например. Context получит Strategy и userId в качестве параметра. Перед выполнением Strategy, Context необходимо предоставить много дополнительной информации, связанной с профилем пользователя. Context получит необходимую информацию и выполнит стратегический метод Стратегии. В отсутствие контекста вам придется дублировать код в 100 разных местах, если вы вызываете стратегический метод в 100 разных местах.

  3. Context может самостоятельно принять решение о том, какую стратегию использовать. Он может просто изменить тип стратегии в зависимости от конфигурации времени выполнения. Основой стратегии USP является переключение между семействами связанных алгоритмов. Контекст - лучшее место для достижения этого.

  4. Если вам нужно действовать по нескольким стратегиям, Context - лучшее место. Предложенный Axtavt ответ с использованием Accumulator является одним примером.

См. Этот пост более подробно.

Пример шаблона стратегии в реальном мире

...