НЕ МОЛЧИ!!!    Сделай что-нибудь, чтобы остановить войну России в Украине.
...бойтесь людей равнодушных - именно с их молчаливого согласия происходят все самые ужасные преступления на свете.   ("Репортаж с петлёй на шее")

Шаблон проектирования Сomposite(Компоновщик)

Описывается структурный шаблон проектирования Сomposite, его использование и пример реализации.

Содержание

  • Область применения
  • Реализация
  • Итоги

Область применения

Шаблон Сomposite применяют для упрощения взаимодействия со сложными иерархическими структурами приложения за счет единого поведения всех ее элементов. Каким же образом и в каких случаях это возможно? В том случае, когда иерархия такой структуры можно построить на основе единого интерфейса всех ее элементов. В таком случае клиент подобной структуры(читай — приложение) может обращаться к любому элементу не задумываясь о специфике запрашиваемого компонента, будь то простейший примитивный элемент или сложный составной объект.
Достаточно наглядно реализацию описанной структуры представляет меню приложения. Пункт меню может быть как самостоятельным простейшим элементом, так и представлять собой подменю, в котором содержатся другие пункты. Само меню может быть сложным по структуре, но его элементы могут быть либо простыми ссылками или командами либо сложными объектами, содержащими другие простые элементы меню. В итоге клиенту будет доступен единый интерфейс для взаимодействия с любым пунктом меню, каким бы сложным это меню ни было.

Для создания единого интерфейса нашего меню определим некий исходный Component, в котором определим набор необходимых методов, для простоты пусть будет три метода.

Исходный компонент интерфейса меню

Итак, есть Component, определяющий набор методов нашего меню для взаимодействия с клиентами. Он может быть реализован как интерфейс или абстрактный класс.

Ниже на диаграмме показан фрагмент меню, каждый элемент которого реализует методы, объявленные в Component. Данное меню состоит из примитивных объектов Item или составных Submenu, содержащих дочерние Item или Submenu. Уровень вложенности не ограничен. Заметим, что Item, конечно, не может реализовать методы, предназначенные для подменю, например, добавить(add()) или удалить(remove()) пункт меню в свой состав. В простейшем случае такие методы будут у него могут быть реализованы пустыми.

Фрагмент меню приложения

Теперь займемся реализацией вышесказанного. Создаваемая структура будет немного отличаться от вышеописанного, это сделано для большей практической ценности создаваемого меню приложения. Если точнее, то вместо расплывчатого метода operation() будут вполне конкретные методы.

Реализация

Вот как шаблон Composite может быть реализован в коде. Вначале определим набор методов в базовом интерфейсе Component.

interface Component
{
    public function getName();
    public function isSimple();

    public function get($key);
    public function add($item);
    public function remove($key);
}

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

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

class Item implements Component
{

    protected $simple = true;

    protected $name;

    public function __constructor($itemName)
    {
        $this->name = $itemName;
    }

    public function getName()
    {
        return $this->name; 
    }

    public function isSimple()
    {
        return $this->simple;
    }

    public function get($key)
    {
        return null;
    }

    public function add($item)
    {
        return false; 
    }

    public function remove($key)
    {
        return false; 
    }
}

Далее определяем сложный компонент меню, который содержит как простые, так с сложные элементы меню, которые содержат дочерние объекты в переменной $list.

class Submenu implements Component
{

    protected $simple = false;

    protected $name;
 
    protected $list = [];

    public function __constructor($itemName)
    {
        $this->name = $itemName;
    }

    public function getName()
    {
        return $this->name; 
    }

    public function isSimple()
    {
        return $this->simple;
    }

    public function get($key)
    {
        if (array_key_exists($key, $this->list)) {
            return $this->list[$key];
        } else {
            return null;
        }
    }

    public function add($item)
    {
        $this->list[$item->name] = $item;
        
    }

    public function remove($key)
    {
        if (array_key_exists($key, $this->list)) {
            unset($this->list[$key]);
            return true;
        } else {
            return false;
        }
    }
}

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

class App
{
    $menuList = array('File', 'View', 'Help');

    protected function getTypeByName($name)
    {
        //определяет тип элемента и возвращает логическое значение
    }

    public function createMenuArray()
    {
    $менuArr = [];
    for ($i = 0; $i < count($this->menuList); $i++) {
        $item = $this->createMenuObject($this->menuList[$i]);  /* создаем объект класса Item или Submenu */
        $менюArr[] = $item;
    }
    return $menuArr;    //возвращает массив объектов меню
    }

    public function createMenuObject($name)
    {
        $isSimple = $this->getTypeByName($name);   //определяет тип элемента
        if ($isSimple) {
            $obj = new Item($name);
        } else {
            $obj = new Submenu($name)
            // наполняем массив $obj->list дочерними элементами
        }
         // ...
        return $obj;
    }
}

Итоги

Итак, было показано, что используя шаблон проектирования Сomposite, мы можем упростить код приложения. В чем же простота?
Прежде всего упрощается логика за счет унификации его взаимодействия приложения со своими достаточно сложными элементами.
Использование шаблона позволяет достаточно просто модифицировать иерархию подобных структур мало затрагивая код приложения.
Но следует забывать, что шаблон Сomposite применим в случае взаимодействия с достаточно однородной древовидной структурой объектов.