Pattern Strategy – поведенческий шаблон проектирования “Стратегия”

9

Продолжу цикл статей, посвященный паттернам проектирования описанием паттерна 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/

Patterns

Комментарии (9 комментариев)

  1. Veresc, сентября 3, 2010 at 19:14 Сказал:

    Спасибо хорошая статья. Не понимаю только а какую нагрузку несет класс, реализующий паттерн? В чем его практическая ценность?

  2. Олег Мурашов, сентября 3, 2010 at 20:08 Сказал:

    Даже не знаю, что добавить к тому, что уже написано в статье. Мне кажется, вам стоит еще раз перечитать заметку и обратить особое внимание на примеры кода.

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

    Прочтите статью еще раз, внимательно, и попробуйте, например, реализовать классы работы с кэшем. Допустим, один с Memcached, а другой с кэшем на файлах. При этом используйте Pattern Strategy. Я думаю, вы осознаете удобство и все преимущества.

  3. Классная статья, только начал знакомиться с патернами, так что человеческое спасибо!!!!

  4. Олег Мурашов, декабря 17, 2010 at 2:40 Сказал:

    Александр, всегда пожалуйста. Рад, что моя заметка оказалась вам полезной

  5. Очень интересно пишите, спасибо. От себя хотел бы пожелать написать какую-нибудь заметку с примерами практического использования, в идеале – написание своего простенького фреймворка.

  6. Олег Мурашов, марта 25, 2011 at 18:38 Сказал:

    Вы забегаете немного вперед :) Framework я планирую начать писать примерно через месяц. В принципе, если будет время, попробую написать несколько сопровождающих статей. Подписывайтесь на RSS, если действительно интересно.

  7. Алексей, июля 5, 2011 at 16:05 Сказал:

    Очень, нет ОЧЕНЬ важно показывать не оторванные от жизни примеры реализации того или иного шаблона.
    Вам удалось то, что не всегда удается всяким умным книжкам в которых примеры из разряда космических кораблей в большом театре.
    Большое вам спасибо.

  8. Вячеслав, ноября 29, 2011 at 11:17 Сказал:

    Очепятка в классе Config_Config в методе get надо вместо:
    $this->_instance->write($key);
    написать:
    $this->_instance->get($key);

  9. Олег Мурашов, декабря 13, 2011 at 4:14 Сказал:

    Вячеслав, спасибо. Исправил опечатку.

Оставить комментарий