Strategy

Назначение

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


Иерархии наследования работают только до тех пор,
пока не надо шарить поведение горизонтально

  • Позволяет комбинировать разные реализации, которые в противном случае требовалось бы копировать в разные классы.
  • Избавляет от экспоненциального роста количества классов, так как после реализации шаблона можно комбинировать произвольные стратегии, соединяя в одном экземпляре различное поведение.
  • Избавляет от необходимости использовать длинные ветки if-else и switch-case
  • Разрешает менять поведение объекта в runtime в зависимости от выбранной реализации стратегии, подобно шаблону State.
  • Вычленяет код и зависимости из класса-контекста в отдельный класс-реализацию конкретной стратегии, то есть изолирует эту часть логики.
  • Соблюдает принцип Open/Closed - стратегии можно добавлять

Описание

Допустим есть некоторое количество классов, у которых есть часть общего функционала и часть специфичного. Причём специфичный функционал используется необязательно единственным классом, он может быть задействован например, в 2-3 классах. Простейший подход к решению этой проблемы - использовать иерархию наследования. Но не всегда можно построить такую иерархию. Например, бывает необходимость использовать общую реализацию в разных ветках иерархии (см. рисунок). Шаблон Strategy объединяет семейство алгоритмов / типов поведения (или стратегий) единым интерфейсом, тем самым позволяя клиенту использовать алгоритм без знания о конкретном классе, реализующем алгоритм. Клиент получает экземпляр через механизм Dependency Injection (DI). Поэтому можно менять алгоритм без изменения кода клиента, в том числе во время работы.


Если мы создадим общего предка для MountingDuck, CloudDuck,
то мы всё равно не сможем разрешить проблему, которая возникнет при
необходимости горизонтального переиспользования кода

Частично шаблон может быть заменен применением объектов-функций (лямбд).

Проблема:

  • где-то клиент, создающий контекстный класс должен определить какую именно стратегию требуется создать;
  • согласно принципу Interface segregation, интерфейсы должны быть как можно более узкими, объекты не должны через интерфейс зависть от методов, которые он не хочет. Иногда требуется сделать объект без какого-либо поведения (та же нелетающая резиновая утка), но этот метод всё равно будет определён в интерфейсе стратегии.

Реализация

Client
Клиент, который создает (конструирует) экземпляр класса Context
Context
Duck
Класс, инкапсулирующий общие характеристики и поведение для всех видов объектов. Меняет свое поведение в зависимости от установленной через конструктор/setter стратегии. Имеет ссылку на стратегию через тип интерфейса.
Strategy
QuackStrategy, FlyStrategy, DisplayStrategy, ...
Интерфейс, определяющий контракт всех стратегий, то есть какой функционал должна предоставлять любая стратегия.
ConcreteStrategy
SimpleQuackStrategy, ShortQuackStrategy, FastFlyStrategy, ...
Конкретная реализация стратегии, содержащая в себе какие-либо особенности. Конкретная стратегия внедряется в Context через DI по ссылке типа Strategy.

Примеры

Варианты

  1. Можно сделать стратегию, которая ничего не делает - получится Null Object.
  2. Можно для стратегий использовать абстрактный суперкласс.

    Чем отличается

Command определяет какое именно действие нужно сделать, а Strategy определяет как делать действие, способ или алгоритм его выполнения. Command часто не содержит логику самого действия, а просто инкапсулирует параметры и вызов на конкретном объекте некоторого метода. Strategy часто определяет именно содержательную часть логики, по которой выполняется действие.

Strategy - Quicksort / Mergesort - как

Command - Open / Close - что

Strategy обычно принимает параметры в методе, а команда инкапсулирует их в себе. Команда может выполняться отложено, Strategy в момент вызова. Strategy обычно несколько объектов (т.к. это штука без состояния), а объектов команды – множество, команда имеет состояние и завязана на конкретный объект, у которого она будет дергать метод.

Template Method позволяет менять внутренности метода, но через наследование и переопределение, а не через делегирование, как Strategy. Наследование статично, в отличие от делегирования.

Bridge является логическим расширением шаблона Strategy, то есть частный случай Bridge c одним классом абстракции по сути и есть Strategy.

Observer можно рассматривать в некотором смысле как частный случай Strategy. Способ реагирования на измененное состояние наблюдаемого объекта можно назвать стратегией.

Decorator через делегирование позволяет менять поведение объекта.

Ссылки

https://java-design-patterns.com/patterns/strategy/

https://github.com/iluwatar/java-design-patterns/tree/master/strategy

https://refactoring.guru/design-patterns/strategy

Baeldung - Strategy Design Pattern in Java 8

YouTube: Strategy Pattern – Design Patterns (ep 1)