Также известен как Strategy

Стратегия

Суть паттерна

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

Проблема

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

Первая проблема заключается в том, что вам приходится держать код алгоритмов в классе контекста, загромождая его основную бизнес-логику.

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

Решение

Паттерн Стратегия предлагает вынести все эти алгоритмы из контекста в собственные классы, называемые стратегиями. Объект контекст будет содержать ссылку на один-единственный объект-стратегию, которому и будет делегироваться выполнение.

Так как все объекты-стратегии будут иметь одинаковый интерфейс, вы сможете при желании взаимозаменять их в контексте на лету.

Аналогия из жизни

Транспорт

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

Пример паттерна Стратегия №1

Инструменты

Рабочий может забить гвоздь обычным молотком. Или пневматическим. Или камнем. Все эти инструменты не встроены в тело человека, поэтому он волен выбирать инструмент в зависимости от ситуации.

Пример паттерна Стратегия №2

Структура

Схема структуры классов паттерна Стратегия
  1. Контекст хранит ссылку на объект Конкретной Стратегии. Работает с этим объектом только через интерфейс Стратегии.

  2. Стратегия определяет интерфейс, общий для всех вариаций алгоритма. Контекст использует этот интерфейс для вызова алгоритма.

    Для Контекста не важно какая именно вариация алгоритма будет выбрана, так как все они имеют одинаковый интерфейс.

  3. Конкретная Стратегия — реализация одной из версий алгоритма.

  4. Во время выполнения программы, Контекст получает вызовы от Клиента и делегирует их объекту Конкретной Стратегии.

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

Псевдокод

В этом примере, Контекст выбирает Стратегию для выполнения той или иной арифметической операции.

// Общий интерфейс всех стратегий.
interface Strategy is
    method algorithm(a, b)

// Каждая конкретная стратегия реализует общий интерфейс своим способом.
class ConcreteStrategyAdd implements Strategy is
    method algorithm(a, b) is
        return a + b

class ConcreteStrategySubtract implements Strategy is
    method algorithm(a, b) is
        return a - b

class ConcreteStrategyMultiply implements Strategy is
    method algorithm(a, b) is
        return a * b

// Контекст (как клиент) всегда работает со стратегиями через общий интерфейс.
// Он не знает какая именно стратегия ему подана.
class Context is
    private strategy: Strategy

    method setStrategy(Strategy strategy) is
        this.strategy = strategy

    method executeStrategy(int a, int b) is
        return strategy.execute(a, b)


// Конкретная стратегия конфигурируется на более высоком уровне, например,
// конфигуратором всего приложения. Готовый объект-стратегия подаётся в
// клиентский объект. Он может быть заменён другой стратегией в любой момент
// на лету.
class ExampleApplication is
    method main() is
        Create context object.

        Read first number
        Read last number
        Read the desired action from user input

        if (action == addition) then
            context.setStrategy(new ConcreteStrategyAdd());

        if (action == subtraction) then
            context.setStrategy(new ConcreteStrategySubtract());

        if (action == multiplication) then
            context.setStrategy(new ConcreteStrategyMultiply());

        result = context.executeStrategy(First number, Second number);

Применимость

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

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

У вас есть множество похожих классов, отличающихся только некоторым поведением.

Стратегия позволяет объединить эти классы в один, сделав поведение настраиваемым.

Алгоритм использует данные, которые нежелательно показывать клиентам этого класса.

Стратегия позволяет скрыть данные и детали реализации каждого алгоритма внутри отдельных классов.

Различные вариации алгоритмов реализованы в виде развесистого условного оператора. Каждая ветка такого оператора представляет вариацию алгоритма.

Стратегия помещает каждую лапу такого оператора в отдельный класс-стратегию.

Затем контекст получает определённый объект-стратегию от клиента и делегирует ему работу. Если вдруг понадобится сменить алгоритм, в контекст можно подать другую стратегию.

Шаги реализации

  1. Определите алгоритм, который подвержен частым изменениям. Также подойдёт алгоритм, имеющий несколько вариаций, которые выбираются во время выполнения программы.

  2. Создайте интерфейс Стратегии, описывающий этот алгоритм. Он должен быть общим для всех вариантов алгоритма.

  3. Поместите вариации алгоритма в собственные классы — Конкретные стратегии — которые реализуют этот интерфейс.

  4. В контексте привязывайтесь к общему интерфейсу алгоритма, а не к конкретным классам реализациям.

Преимущества и недостатки

  • Горячая замена алгоритмов на лету.
  • Уход от наследования к делегированию.
  • Реализует принцип открытости/закрытости.
  • Скрывает опасные/лишние данные алгоритма от Клиента.
  • Усложняет программу за счёт дополнительных классов.
  • Клиент должен знать в чём разница между стратегиями, чтобы выбрать подходящую.

Отношения с другими паттернами

  • Мост, Стратегия и Состояние (а также слегка и Адаптер) имеют схожие структуры классов — все они построены на принципе «композиции», то есть делегирования работы другим объектам. Тем не менее они отличаются тем, что решают разные проблемы. Помните, что паттерны — это не только рецепт построения кода определённым образом, но и описание проблем, которые привели к данному решению.

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

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

  • Шаблонный метод использует наследование, чтобы расширять части алгоритма. Стратегия использует делегирование, чтобы изменять алгоритм. Шаблонный метод работает на уровне классов. Стратегия позволяет менять логику отдельных объектов.

  • Стратегия меняет поведение объекта «изнутри», а Декоратор изменяет его «снаружи».

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

Реализация в различных языках программирования

Java