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