Strategy (Стратегия)¶
Категория: паттерн поведения.
Проблема¶
Есть задача, которую можно решить несколькими взаимозаменяемыми алгоритмами (расчёт скидки, сортировка, сжатие, валидация), и выбор конкретного алгоритма должен происходить в рантайме, а не быть жёстко зашит в код. Наивное решение - один метод с большим if/switch по типу алгоритма - работает, но:
- при добавлении нового варианта приходится модифицировать существующий метод (нарушение принципа открытости/закрытости);
- логика разных алгоритмов перемешивается в одном месте, что усложняет чтение и тестирование каждого варианта по отдельности.
Решение¶
- Для семейства алгоритмов выделяется общий интерфейс с одним (реже - несколькими) методом.
- Каждый конкретный алгоритм оформляется отдельным классом, реализующим этот интерфейс.
- Класс-контекст, которому нужен алгоритм, хранит ссылку на интерфейс стратегии (а не на конкретную реализацию) и делегирует ей выполнение операции.
- Конкретная реализация подставляется в контекст снаружи - через конструктор, свойство или параметр метода.
Структура¶
Strategy- общий интерфейс алгоритма.ConcreteStrategyA/B/...- конкретные реализации алгоритма.Context- хранит ссылку наStrategyи делегирует ей выполнение операции, не зная, какая именно реализация подставлена.
Когда применять¶
- Нужно выбирать между несколькими вариантами одного и того же поведения в рантайме.
- Метод содержит разрастающийся
if/switch, различающий поведение по типу/режиму. - Нужно изолировать детали конкретного алгоритма от кода, который им пользуется, чтобы упростить тестирование каждого варианта по отдельности.
Плюсы¶
- Новый алгоритм добавляется новым классом, без изменения существующего кода контекста (открытость/закрытость).
- Каждую стратегию можно тестировать в изоляции.
- Убирает разрастающиеся условные конструкции по типу поведения.
Минусы¶
- Клиентский код должен знать о различиях между стратегиями, чтобы выбрать подходящую - паттерн не решает вопрос "как выбрать стратегию", он лишь упрощает её подстановку.
- Для очень простых, не имеющих состояния алгоритмов создание отдельного интерфейса и класса может быть избыточным.
Интерфейс vs делегат¶
В C# для стратегий без собственного состояния и с одной операцией часто разумнее использовать не интерфейс с одним методом, а делегат (Func<T, TResult>, Comparison<T>, Predicate<T>) - это тот же паттерн Strategy, но выраженный через встроенный в язык механизм функций как объектов первого класса, без лишней иерархии классов. Интерфейс имеет смысл заводить, когда:
- у стратегии несколько связанных операций;
- реализации нужно хранить собственное состояние или зависимости, которые удобнее внедрять через конструктор класса, а не через замыкание.
Отличие от Template Method¶
Strategy заменяет весь алгоритм целиком через композицию (контекст содержит ссылку на объект-стратегию). Template Method фиксирует скелет алгоритма в базовом классе и позволяет переопределить только отдельные шаги через наследование. Strategy гибче (можно менять поведение в рантайме, без наследования), Template Method - проще для случаев, когда общей логики между вариантами больше, чем различий.
Пример в .NET Framework / BCL¶
IComparer<T>и делегатComparison<T>, используемые вList<T>.Sort(),Array.Sort().IEqualityComparer<T>в словарях иHashSet<T>.- Middleware-компоненты авторизации (
IAuthorizationHandler) в ASP.NET Core - разные стратегии проверки прав доступа.