Стратегия (Strategy)

Strategy выносит взаимозаменяемые варианты алгоритма за общий интерфейс. Context делегирует вычисление выбранной стратегии и не содержит цепочку if по каждому варианту.

Пример

public interface DistributionStrategy {
    List<BigDecimal> distribute(
            BigDecimal total,
            int parts
    );
}
 
public final class EqualDistribution
        implements DistributionStrategy {
 
    @Override
    public List<BigDecimal> distribute(
            BigDecimal total,
            int parts
    ) {
        // алгоритм равного распределения
        return List.of();
    }
}

Context получает strategy через constructor:

public final class DistributionService {
    private final DistributionStrategy strategy;
 
    public DistributionService(
            DistributionStrategy strategy
    ) {
        this.strategy = strategy;
    }
 
    public List<BigDecimal> calculate(
            BigDecimal total,
            int parts
    ) {
        return strategy.distribute(total, parts);
    }
}

Выбор может происходить при конфигурации object graph или во время запроса:

DistributionStrategy strategy = switch (mode) {
    case EQUAL -> new EqualDistribution();
    case WEIGHTED -> new WeightedDistribution();
};

Сам switch не исчезает обязательно. Он перемещается в одну точку выбора, а реализации алгоритмов остаются отдельными.

Когда полезна

  • варианты имеют общий input/output contract;
  • каждый алгоритм содержит самостоятельную логику;
  • варианты нужно тестировать отдельно;
  • выбор меняется без изменения context;
  • условная ветка начала расти в нескольких местах.

Для двух коротких stateless операций отдельная иерархия классов может быть лишней. В Java стратегией может быть обычная функция:

UnaryOperator<BigDecimal> rounding =
        value -> value.setScale(0, RoundingMode.DOWN);

Strategy и Template Method

StrategyШаблонный метод Template Method
композициянаследование
заменяется весь алгоритмзаменяются отдельные steps
вариант можно выбрать runtimeвариант задаёт subclass
context делегируетbase class управляет порядком

Типичные ошибки

  • interface слишком широкий и заставляет варианты игнорировать параметры;
  • context знает внутренние детали конкретной strategy;
  • strategy хранит mutable state и неожиданно разделяется потоками;
  • новый класс создаётся для однострочной функции без причины;
  • выбор размазан по controllers вместо одной factory/configuration.

Вопросы для собеседования

  1. Что меняется независимо? Реализация алгоритма.
  2. Обязателен ли interface? Нет, роль может выполнять functional interface.
  3. Исчезает ли условный выбор? Не обязательно; он концентрируется в месте сборки.
  4. Главное отличие от Template Method? Композиция вместо наследования и замена алгоритма целиком.
  5. Когда паттерн избыточен? Когда вариант один или операция выражается простой функцией.

Связанные заметки