В программной инженерии компоновщик позволяет обращаться к группам объектов так, как будто они являются отдельными объектами, делая общую структуру этих объектов и их комбинаций единообразной.
Главная задача компоновщика — объединение множества объектов в единую древовидную структуру. В этом случае иерархия формируется по принципу от частного к целому.
Суть в том, что каждый объект коллекции — это часть общей композиции. Композиция, в свою очередь, является коллекцией ее частей.
Иерархия от частного к целому строится как древовидная структура, где каждый отдельный лист или узел воспринимается одинаково в любой части дерева. Это означает, что группа или коллекция объектов (поддерево узлов) также является листом или узлом. Это выглядит так:
Теперь, когда у нас есть более четкое представление принципа отношения между частным и целым, давайте вернемся к компоновщику. Мы определили, что его цель в структурировании объектов в дерево согласно этому принципу.
Следовательно, мы получаем шаблон проектирования, в котором каждый элемент коллекции может также присоединять коллекцию, выстраивая глубокие структуры.
Внутреннее строение
Каждый узел в дереве разделяет общий набор свойств и методов, позволяющих ему поддерживать отдельные объекты, взаимодействуя с ними так же, как с коллекциями объектов.
Такой интерфейс предполагает построение рекурсивных алгоритмов, перебирающих каждый объект в коллекции композита.
Где применяется этот шаблон?
Операционные системы применяют этот шаблон для реализации многих возможностей, позволяя, к примеру, создавать директории внутри директорий.
Файлы (для удобства содержимое директории можно считать элементами) являются листьями/узлами (частями) внутри этого композита (директории).
Созданная поддиректория в директории аналогично является листом или узлом, включающим в себя другие элементы вроде видео, изображений и прочего. В то же время и директории, и поддиректории являются композитами, поскольку представляют собой коллекции частей: объектов, файлов и тому подобного.
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
композитом:
тогда бы наше дерево продолжило развитие:
В итоге мы бы достигли той же цели подписания всех необходимых документов:
В этом и состоит польза компоновщика.
Заключение
Надеюсь, что эта информация оказалось для вас полезной.
Читайте также:
- Шаблон Медиатор в JavaScript
- Функциональное программирование в JavaScript: руководство с практическими примерами
- Зачем нужен Strict Mode в JavaScript?
Перевод статьи jsmanifest: The Power of the Composite Design Pattern in JavaScript