Pull to refresh

Компоновщик стратегий или объединение Composite+Strategy

Reading time 3 min
Views 1.4K
Всем привет!

Сегодня я хочу вам показать результат взаимодействия двух широкоро распространенных паттернов проектирования Strategy и Composite, в результате чего у нас получится так называемый «Компоновщик стратегий».

Проблема


Необходимо создать механизм конструирования стратегий для их совместного применения. В жизни может быть использована, например, когда при возникновении нескольких событий выполнять какое-либо действие, события могут быть, например, такие: зарегистрировался пользователь, пользователь заполнил дату рождения, пользователь не заполнил свои паспортные данные — в результате совпадения такой цепочки событий, к примеру, отсылаем нашему пользователю email с просьбой заполнить соответствующее поле в анкете. Такие проверки можно осуществлять, к примеру, асинхронно.

Для реализации данной задачи совместим реализации паттернов Strategy и Composite.

Задача паттерна Strategy


Из Wikipedia: Выбор алгоритма, который следует применить, в зависимости от типа выдавшего запрос клиента или обрабатываемых данных.

Цель паттерна Composite


Из Wikipedia: Паттерн определяет иерархию классов, которые одновременно могут состоять из примитивных и сложных объектов, упрощает архитектуру клиента, делает процесс добавления новых видов объекта более простым.

Для желающих сразу посмотреть реализацию: GitHub source

Сначала определимся, как мы хотим вызывать нашего компоновщика:
$composite = new \CompositeAndStrategy\CompositeStrategy(
    new \CompositeAndStrategy\CompositeStrategyAnd(
        new \CompositeAndStrategy\CompositeStrategyOr(
            new \CompositeAndStrategy\StrategyFirst(),
            new \CompositeAndStrategy\StrategySecond()
        ),
        new \CompositeAndStrategy\StrategyThird()
    ),
    new \CompositeAndStrategy\CompositeStrategyOr(
        new \CompositeAndStrategy\StrategyFourth(),
        new \CompositeAndStrategy\StrategyFifth()
    )
);

$result = $composite->perform();


В результате чего мы хотим получить стратегию, которая выполнилась успешно. Это может быть или единичный объект или коллекция объектов. (В данном примере, мы видим 2 основные сложные стратегии)

Интерфейс стратегии и компоновщика(т.к. в нашем случае компоновщик сам является стратегией):
namespace CompositeAndStrategy;

interface IStrategy
{
    function perform();
}


Интерфейс компоновщика (самое необходимое):
namespace CompositeAndStrategy;

interface ICompositeStrategy
{
    function getAll();
    function add(IStrategy $strategy);
}


Напишем базовый компоновщик:
namespace CompositeAndStrategy;

class CompositeStrategy implements ICompositeStrategy, IStrategy
{
    public function __construct()
    {
        $strategies = func_get_args();
        if ($strategies) {
            foreach($strategies as $strategy) {
                if ($strategy instanceof IStrategy) {
                    $this->add($strategy);
                }
            }
        }
    }
    /**
     * @var IStrategy[]
     */
    protected $collection;

    /**
     * @param IStrategy $strategy
     */
    public function add(IStrategy $strategy)
    {
        $this->collection[] = $strategy;
    }

    /**
     * @return IStrategy[]
     */
    public function getAll()
    {
        return $this->collection;
    }

    /**
     * @return IStrategy
     */
    public function perform()
    {
        foreach($this->getAll() as $strategy) {
            if ($strategy->perform()) {
                return $strategy;
            }
        }
    }
}

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

Далее мы будем расширять реализованный выше базовый класс компоновщика. Реализуем стратегию-компоновщик, которую считаем выполненной, если выполняются все ее стратегии (логическая операция «И»).
namespace CompositeAndStrategy;

class CompositeStrategyAnd extends CompositeStrategy
{
    /**
     * @return bool|CompositeStrategyAnd
     */
    public function perform()
    {
        foreach($this->getAll() as $strategy) {
            if (!$strategy->perform()) {
                return false;
            }
        }
        return $this;
    }
}

Далее по аналогии реализуем стратегию-компоновщик, которую считаем выполненной, если выполняется хотя бы одна ее стратегия (логическая операция «ИЛИ»).
namespace CompositeAndStrategy;

class CompositeStrategyOr extends CompositeStrategy
{
    /**
     * @return CompositeStrategyOr
     */
    public function perform()
    {
        foreach($this->getAll() as $strategy) {
            if ($strategy->perform()) {
                return $this;
            }
        }
    }
}


Нам остается реализовать наши стратегии:

Первая и основная:
namespace CompositeAndStrategy;

class StrategyFirst implements IStrategy
{
    /**
     * @param $bool
     * @return mixed
     */
    protected function drawLog($bool)
    {
        echo get_called_class().' - ' . (int)$bool.'<hr />';
        return $bool;
    }

    /**
     * @return bool|StrategyFirst
     */
    public function perform()
    {
        if ($operation = $this->drawLog(rand(0, 1))) {
            return $this;
        }

        return false;
    }
}


И еще 4 стратегии:
namespace CompositeAndStrategy;

class StrategySecond extends StrategyFirst
{

}

namespace CompositeAndStrategy;

class StrategyThird extends StrategyFirst
{

}

namespace CompositeAndStrategy;

class StrategyFourth extends StrategyFirst
{

}

namespace CompositeAndStrategy;

class StrategyFifth extends StrategyFirst
{

}


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

Комментарии и критика приветствуются :)
Удачных выходных!
Tags:
Hubs:
+2
Comments 9
Comments Comments 9

Articles