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

Главная задача компоновщика  —  объединение множества объектов в единую древовидную структуру. В этом случае иерархия формируется по принципу от частного к целому.

Суть в том, что каждый объект коллекции  —  это часть общей композиции. Композиция, в свою очередь, является коллекцией ее частей.

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

Теперь, когда у нас есть более четкое представление принципа отношения между частным и целым, давайте вернемся к компоновщику. Мы определили, что его цель в структурировании объектов в дерево согласно этому принципу. 

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

Внутреннее строение

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

Такой интерфейс предполагает построение рекурсивных алгоритмов, перебирающих каждый объект в коллекции композита.

Где применяется этот шаблон?

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

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

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

React или Vue широко применяют этот шаблон для построения надежных и переиспользуемых интерфейсов. Все элементы веб-страниц, которые вы видите  —  это компоненты. 

Каждый компонент  —  это лист древа и он может объединять множество компонентов, образуя другой лист. При этом образуется композит, который по-прежнему остается листом дерева.

Это мощный принцип, упрощающий разработку. Помимо этого, он позволяет создавать масштабируемые приложения, задействующие множество объектов.

Зачем же нам нужен этот шаблон?

Он дает возможность обращаться к объекту, как к композитному объекту. Это оказывается возможным благодаря общему интерфейсу. 

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

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

Примеры

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

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

Мы также определяем в этом классе метод sign, осуществляющий эту функцию. Так будет выглядеть Document:

Теперь, применив компоновщик, мы включим те же методы, которые определены в Document.

Теперь становится очевидна изящность шаблона. Обратите внимание на два последних сниппета кода:

Отлично! Похоже мы на верном пути, о чем можно судить по соответствию текущей и предыдущей диаграмм:

Итак, наше дерево содержит два узла — Document и DocumentComposite. Оба они разделяют один и тот же интерфейс, и, следовательно, действуют как части единого композитного дерева. 

Здесь следует отметить, что лист/узел древа, не являющийся композитом (Document), не продолжит разветвления, т.к. не является коллекцией объектов. 

Тем не менее составной узел содержит коллекцию частей (в нашем случае items). Запомните также, что Document и DocumentComposite разделяют интерфейс, а значит разделяют и метод sign.

Так в чем же здесь сила?

Несмотря на то, что DocumentComposite разделяет единый интерфейс, выполняя при этом метод sign, аналогично Document, он воплощает в себе более эффективный подход, достигая при этом той же конечной цели. 

Поэтому вместо:

мы можем сделать код эффективнее с помощью компоновщика:

При таком подходе нам потребуется лишь единожды выполнить sign после добавления всех нужных документов, и он подпишет все документы.

Убедиться в этом мы можем, взглянув на вывод console.log(forms):

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

Также не стоит забывать, что наш DocumentComposite способен содержать коллекцию документов. Поэтому, когда мы ввели:

forms.add(pr2Form) // Документ
forms.add(w2Form) // Документ

наша диаграмма превратилась в:

Имея две добавленные формы, она отлично согласуется с нашей изначальной диаграммой:

Тем не менее наше дерево прекращает свой рост: последний его узел образовал только два листа, что не совсем соответствует приведенной выше диаграмме. Если бы вместо этого мы сделали форму w2form композитом:

тогда бы наше дерево продолжило развитие:

В итоге мы бы достигли той же цели подписания всех необходимых документов:

В этом и состоит польза компоновщика.

Заключение

Надеюсь, что эта информация оказалось для вас полезной.

Читайте также:


Перевод статьи jsmanifest: The Power of the Composite Design Pattern in JavaScript

Предыдущая статьяШаблон Медиатор в JavaScript
Следующая статья4 совета по улучшению Jupyter Notebooks