Продолжу цикл статей, посвященный паттернам проектирования описанием паттерна Strategy (стратегия), также известным как Policy.
Strategy – это поведенческий шаблон проектирования, применимый там, где для решения одной и той же задачи могут использоваться различные алгоритмы. Важным моментом является реализация взаимозаменяемости алгоритмов.
Чтобы было понятней, рассмотрим более или менее живой пример. Допустим, ваше приложение должно уметь работать с несколькими типами конфигурационных файлов: XML, INI и т.п. В действительности, набор может быть каким угодно.
Мы можем убрать один из типов конфигурационных файлов или наоборот добавить. Это не должно стать причиной того, что нам придется переписывать код приложения.
И так, представим, что на каждый алгоритм работы с определенным типом конфига у нас описан свой класс. Каждый класс обладает своим набором методов и свойств, то есть имеет свой интерфейс доступа, отличный от других классов набора. Замена одного алгоритма на другой станет непосильной задачей. Придется переписать весь код, где описана работа с конфигурационными файлами, чтение, запись или другие операции
В коде у нас могло бы быть нечто вот такое:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <?php $configModel = 'INI'; if ($configModel == 'INI') { $config = new Config_INI(); } elseif ($configModel == 'XML') { $config = new Config_XML(); } else { // ... } ?> |
Так могла бы выглядеть инициализация объекта нужного нам класса. Несложно представить, чего будет стоить попытка изменить количество алгоритмов. Да чего уж там, переименование класса создаст не меньше проблем. К этому добавьте код, который работает с интерфейсом объекта и перспектива станет совсем унылой
Теперь попробуем сделать тоже, но уже по умному, с использованием поведенческого шаблона проектирования Strategy.
Для начала опишем код интерфейса, который будет имплементироваться всеми классами алгоритмов работы с конфигурационными файлами.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <?php interface Config_Interface { /** * Get contents file of config * * @param string $file * @return boolean */ public function read($file); /** * Put contents file of config * @param mixed $data * @return boolean */ public function write($data); /** * Return value by key * @param string $key * @return mixed */ public function get($key); } ?> |
Интерфейс декларирует три основных метода (на деле их может быть больше, но для примера хватит и трех), которые должны быть реализованы каждым алгоритмом, входящим в набор. Эти методы будут тем самым единым интерфейсом, для работы с объектами классов. Мы загоняем себя в жесткие рамки, чтобы потом нам хорошо жилось.
Далее создаем класс для работы, например, с конфигами XML формата. Логику я опускаю, так как речь не о том, как парсить XML файлы. Главное, понять суть наших действий.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | <?php class Config_XML implements Config_Interface { /** * (non-PHPdoc) * @see Config_Interface#read() */ public function read($file) { // ... $this->toArray($data); } /** * (non-PHPdoc) * @see Config_Interface#write() */ public function write($data) { } /** * (non-PHPdoc) * @see Config_Interface#get() */ public function get($key) { } /** * Convert contents file of config to assoc array * * @param mixed $data * @return array */ private function toArray($data) { } } ?> |
Как видите, интерфейс обязывает нас описать три публичных метода, объявленных в нем. Но так как каждый тип конфигурационных файлов требует своей логики работы с данными, помимо этих трех методов, в каждом классе алгоритма у нас будет свой набор дополнительных. Одним из таких можно сделать метод, который преобразует прочитанные из конфига данные в ассоциативный массив или нечто подобное.
Ну а теперь пример класса, реализующего паттерн Strategy:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <?php class Config_Config { /** * Instance of config driver * @var Config_Interface */ private $_instance; public function __construct(Config_Interface $instance) { $this->_instance = $instance; } public function load($file) { $this->_instance->read($file); } public function put($data) { $this->_instance->write($data); } public function get($key) { $this->_instance->get($key); } } ?> |
И сразу пример использования:
1 2 3 4 5 6 | <?php $config = new Config_Config( new Config_XML() ); $config->load('filename.xml'); ?> |
Начнем по порядку. Во-первых, вы наверное заметили, что у классов и интерфейса немного странные имена. Это сделано с расчетом использования прелестей автоматической загрузки классов с помощью __autoload(). Так наше приложение станет более изящным.
Теперь рассмотрим инициализацию объекта от класса стратегии. В качестве параметра конструктора мы передаем объект нужного нам драйвера конфига, в данном случае Config_XML(). Конструктор класса Config_Config может принимать в качестве параметра только объекты имплементирующие интерфейс Config_Interface. Это еще одни рамки, в которые мы загоняем себя для достижения вселенского счастья. За счет этого мы точно знаем, что класс драйвера содержит методы read(), write() и get(). Методы самой стратегии являются оберткой для них.
В итоге мы получили единый интерфейс доступа ко всем алгоритмам набора. Да, где-то нам придется хорошенько извернуться, реализуя логику драйверов таким образом, чтобы она удовлетворяла наложенным нами же ограничениям, но кому сейчас легко?
Можно пойти дальше и сделать объект класса Config_Config доступным глобально. Для этого задействуйте паттерн проектирования Registry, о котором я писал ранее.
Надеюсь, был полезен. Успехов!
Другие паттерны:
Автор: Мурашов Олег
Источник: http://inroot.ru/

Спасибо хорошая статья. Не понимаю только а какую нагрузку несет класс, реализующий паттерн? В чем его практическая ценность?
Даже не знаю, что добавить к тому, что уже написано в статье. Мне кажется, вам стоит еще раз перечитать заметку и обратить особое внимание на примеры кода.
Во-первых, мы обеспечиваем взаимозаменяемость алгоритмов, которые в свою очередь инкапсулированы к отдельные классы. Во-вторых, смена одного алгоримта на другой производится редактированием одной строки кода или, при правильном подходе, это можно сделать через файл конфига, вообще ничего не редактируя. В результате, у нас есть один класс, с единым интерфейсом для всех алгоритмов.
Прочтите статью еще раз, внимательно, и попробуйте, например, реализовать классы работы с кэшем. Допустим, один с Memcached, а другой с кэшем на файлах. При этом используйте Pattern Strategy. Я думаю, вы осознаете удобство и все преимущества.
Классная статья, только начал знакомиться с патернами, так что человеческое спасибо!!!!
Александр, всегда пожалуйста. Рад, что моя заметка оказалась вам полезной
Очень интересно пишите, спасибо. От себя хотел бы пожелать написать какую-нибудь заметку с примерами практического использования, в идеале – написание своего простенького фреймворка.
Вы забегаете немного вперед :) Framework я планирую начать писать примерно через месяц. В принципе, если будет время, попробую написать несколько сопровождающих статей. Подписывайтесь на RSS, если действительно интересно.
Очень, нет ОЧЕНЬ важно показывать не оторванные от жизни примеры реализации того или иного шаблона.
Вам удалось то, что не всегда удается всяким умным книжкам в которых примеры из разряда космических кораблей в большом театре.
Большое вам спасибо.
Очепятка в классе Config_Config в методе get надо вместо:
$this->_instance->write($key);написать:
$this->_instance->get($key);Вячеслав, спасибо. Исправил опечатку.