Предыдущие выпуски: Вступление, MVC
"Стратегия" – довольно обманчивое название, и наверняка вы ошибётесь, если попробуете угадать, о чём оно.
Вкратце суть шаблона выражается так:
Набор алгоритмов, выбираемых во время выполнения программы.
Собственно, всё. Может быть, выбор алгоритма во время выполнения программы – это и есть стратегия? Ну, наверное, да. Давайте разберёмся подробнее.
Какая проблема решается?
Обычно проблему иллюстрируют в виде посетителя кафе или автомойки, который делает заказ. Создаётся счёт, куда должны быть вписаны услуги и суммы. Но вот беда – у заведения есть "счастливые часы", в которые действуют льготные тарифы. Значит, в эти часы суммы должны быть другие.
Как решается эта задача наивным способом? У нас есть объект "счёт", в который добавляются услуги в виде пары (название, цена).
Назовём объект счёта bill, а метод добавления услуг – addItem(). Допустим, мы добавляем в счёт услугу "drink" с ценой 100:
bill.addItem("drink", 100);
Но есть пара условий: клиент может быть VIP, или услуга оказывается в "счастливые часы".
Значит, при добавлении услуги в счёт надо пересчитать её цену. Для этого придётся проверить условия:
- Если клиент VIP, пересчитать цену по одному правилу.
- Если сейчас "счастливые часы", пересчитать цену по другому правилу.
Проблема заключается в том, что метод addItem() должен откуда-то знать, что бывают VIP-клиенты и бывают "счастливые часы", а также какой сейчас час и т.д. А также он должен знать, как именно пересчитывать цены. То есть вся эта логика должна быть реализована внутри метода.
Во-первых, это нагружает метод несвойственными ему обязанностями (то есть бизнес-логикой). Во-вторых, компоненты системы становятся тесно связанными. Метод addItem() начинает требовать, чтобы у него был доступ к объекту-клиенту (чтобы узнать, VIP он или нет), к расписанию заведения, к бизнес-логике и т.д. Любые изменения в этих компонентах будут приводить к необходимости переписывать метод.
Как работает шаблон Стратегия
Создаётся отдельный набор объектов, которые называются стратегиями: один объект реализует алгоритм коррекции цен для VIP, другой объект реализует алгоритм для "счастливых часов", и третий объект реализует стандартный алгоритм.
Когда мы создаём объект bill, то передаём ему как параметр один из объектов-стратегий. Если пришёл обычный клиент в обычные часы, то для него создаётся объект bill с обычной стратегией. Если пришёл обычный клиент в "счастливые часы", то для него создаётся объект bill со стратегией "счастливых часов". Если же пришёл VIP-клиент, для него создаётся объект bill со стратегией VIP. Например, так:
var vipStrategy = new VIPStrategy();
var bill = new Bill(vipStrategy);
Далее, при добавлении услуги через addItem() объекту bill уже не надо ничего проверять. Он обращается к установленной в нём же стратегии: "скорректируй мне цену".
price = this.strategy.correctPrice(price);
Стратегия возвращает ему скорректированную цену, и bill добавляет услугу и цену в свой список.
Что изменилось
Объект bill уже не лезет не в свои дела. Если в заведении изменятся правила, в объекте счёта не нужно будет ничего изменять. Любое количество новых скидок, акций и прочих бонусов реализуется в виде дополнительных стратегий, любую из которых можно передавать в объект bill. Каждая стратегия – отдельный, изолированный объект, который редактируется независимо от других.
Кроме того, стратегию для объекта bill можно не только задавать при создании, но и менять динамически, написав метод setStrategy():
bill.setStrategy(newStrategy);
Например, часть покупок была внесена в счёт во время "счастливых часов", а часть уже после них. В этом случае по окончании "счастливых часов" стратегия объекта bill заменяется на стандартную и объект работает дальше как ни в чём не бывало.
Что насчёт игр?
В качестве идеального примера можно было бы привести Pac-Man. Смотрите: в лабиринте есть 4 врага, поведение которых меняется в зависимости от состояния Пакмана: они либо догоняют его, либо убегают от него. Это же самый настоящий шаблон Стратегия!
- Если Пакман съел озверин, установить врагам стратегию "избегание"
- Когда действие озверина кончилось, установить врагам стратегию "преследование"
Посмотрим ещё примеры.
В вертикальных шутерах летит самолётик, навстречу которому вылетают толпы врагов.
Одни и те же враги могут на следующих уровнях менять своё поведение. Сначала они будут лететь просто по прямой, затем появляться с боков, затем двигаться по синусоиде и т.д.
Очевидно, что это один и тот же враг, который различается только поведением на экране. И значит, при выпускании их на экран можно устанавливать им стратегии, которые определяют, как они должны двигаться. Да и вообще неважно, кто он. С помощью стратегии вы можете заставить любого врага двигаться по синусоиде, например, не меняя его код.
То же касается и самолётика. Подхватив новый вид оружия, он фактически меняет себе стратегию, которая отвечает за то, как должно стрелять оружие.
В следующем выпуске нас ждет хороший плохой злой шаблон Синглтон.