Стратегия: инкапсуляция алгоритмов
Паттерн «Стратегия» — это инкапсуляция, сокрытие реализации алгоритма за абстракцией, обычно с динамической подменой реализации. Это часто подразумевает использование контейнера для подмены реализации во время выполнения. Диаграмма обычно показывает контекст (пользователь стратегии) и блок «Стратегия». Контекст — это тот, кто использует стратегию; их может быть множество. Главное — блок «Стратегия», являющийся интерфейсом, представляющим общую схему взаимодействия с алгоритмом, абстракцию. Реализацию этого алгоритма можно подменять.
Хотя может показаться, что стратегия — это просто создание интерфейса и его реализации, давайте разберемся подробнее.
Пример реализации: юнит в игре
Рассмотрим код, моделирующий юнит в игре с циклом обновления. Юнит ищет противника и, найдя его, передает ему сообщение через абстрактный метод. Разные юниты используют разные стратегии поиска противников: одни смотрят вперед, другие вокруг, третьи реагируют на звук. Для динамической подмены реализаций, то есть для создания объектов типа «юнит» с разными стратегиями поиска во время выполнения, стратегия выделена через интерфейс. В конструкторе юнита указывается стратегия выбора противника, инкапсулированная в интерфейсе.
Когда стратегия не нужна
Не всегда необходимо выделять стратегию через интерфейс. Если не требуется динамически заменять реализации и передавать внешние зависимости, это усложняет код, делая его более гибким, но менее удобным. Чем гибче код, тем сложнее с ним работать.
Если нет необходимости подменять методы поиска в юнитах, можно просто разложить код, делегируя ответственность, без использования паттерна «Стратегия» и интерфейса. Можно говорить о «стратегии поиска», вложенной в конкретный тип, без абстракции.
Расширение функциональности: дополнительные стратегии
Рассмотрим пример с расширенной абстракцией. Появились юниты, ищущие не только врагов, но и союзников (для лечения или общения). Нам нужна стратегия определения «противник» или «союзник», так как правила проверки зависят от разных атрибутов (заболевания, профессии и т.д.).
Вместо создания нового интерфейса, можно использовать индекс делегатов и событий. Если стратегия выражается одним методом без состояния — простой функцией, принимающей входные данные и возвращающей результат — её можно реализовать с помощью делегата (лямбда-выражения). В данном случае, делегат принимает юнит и возвращает булево значение, определяющее, является ли юнит противником.
Паттерн «Стратегия» — простой и эффективный. Он является неотъемлемой частью многих других паттернов, использующих наследование и интерфейсы.