Найти тему
ZDG

Шаблоны проектирования #3: Стратегия

Предыдущие выпуски: Вступление, MVC

"Стратегия" – довольно обманчивое название, и наверняка вы ошибётесь, если попробуете угадать, о чём оно.

Вкратце суть шаблона выражается так:

Набор алгоритмов, выбираемых во время выполнения программы.

Собственно, всё. Может быть, выбор алгоритма во время выполнения программы – это и есть стратегия? Ну, наверное, да. Давайте разберёмся подробнее.

Какая проблема решается?

Обычно проблему иллюстрируют в виде посетителя кафе или автомойки, который делает заказ. Создаётся счёт, куда должны быть вписаны услуги и суммы. Но вот беда – у заведения есть "счастливые часы", в которые действуют льготные тарифы. Значит, в эти часы суммы должны быть другие.

Как решается эта задача наивным способом? У нас есть объект "счёт", в который добавляются услуги в виде пары (название, цена).

Назовём объект счёта bill, а метод добавления услуг – addItem(). Допустим, мы добавляем в счёт услугу "drink" с ценой 100:

bill.addItem("drink", 100);

Но есть пара условий: клиент может быть VIP, или услуга оказывается в "счастливые часы".

Значит, при добавлении услуги в счёт надо пересчитать её цену. Для этого придётся проверить условия:

  1. Если клиент VIP, пересчитать цену по одному правилу.
  2. Если сейчас "счастливые часы", пересчитать цену по другому правилу.

Проблема заключается в том, что метод 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 врага, поведение которых меняется в зависимости от состояния Пакмана: они либо догоняют его, либо убегают от него. Это же самый настоящий шаблон Стратегия!

-2
  1. Если Пакман съел озверин, установить врагам стратегию "избегание"
  2. Когда действие озверина кончилось, установить врагам стратегию "преследование"

Посмотрим ещё примеры.

В вертикальных шутерах летит самолётик, навстречу которому вылетают толпы врагов.

Одни и те же враги могут на следующих уровнях менять своё поведение. Сначала они будут лететь просто по прямой, затем появляться с боков, затем двигаться по синусоиде и т.д.

Очевидно, что это один и тот же враг, который различается только поведением на экране. И значит, при выпускании их на экран можно устанавливать им стратегии, которые определяют, как они должны двигаться. Да и вообще неважно, кто он. С помощью стратегии вы можете заставить любого врага двигаться по синусоиде, например, не меняя его код.

То же касается и самолётика. Подхватив новый вид оружия, он фактически меняет себе стратегию, которая отвечает за то, как должно стрелять оружие.

-3

В следующем выпуске нас ждет хороший плохой злой шаблон Синглтон.