Composite (Компоновщик)¶
Категория: структурный паттерн.
Проблема¶
Данные естественно образуют иерархию "часть - целое": файлы и папки, элементы интерфейса (кнопка внутри панели внутри окна), узлы дерева выражений, оргструктура компании. Клиентскому коду при этом неудобно постоянно проверять, с чем он имеет дело - с одиночным элементом или с группой элементов - и писать разную логику обработки для каждого случая.
Решение¶
- Вводится единый интерфейс для всех узлов дерева - и для простых ("листьев"), и для составных.
- Лист реализует этот интерфейс напрямую, выполняя операцию над собой.
- Составной узел (Composite) реализует тот же интерфейс, но его реализация операции рекурсивно вызывает ту же операцию у всех своих дочерних элементов и агрегирует результат.
- Клиентский код вызывает операцию на корне дерева, не зная (и не заботясь), сколько уровней вложенности внутри и что где - лист, а что - составной узел.
Структура¶
Component- общий интерфейс для листьев и составных узлов.Leaf- конечный элемент без потомков, реализует операцию напрямую.Composite- хранит коллекцию дочернихComponent(которые сами могут быть иLeaf, иComposite) и делегирует им операцию рекурсивно.Client- работает только через интерфейсComponent.
Когда применять¶
- Предметная область естественно моделируется как дерево "часть - целое".
- Клиентский код должен единообразно работать и с отдельными объектами, и с группами объектов, не различая их явно через
if/проверку типа. - Планируется добавлять новые типы узлов (новые виды листьев или составных элементов) без изменения кода, который обходит дерево.
Плюсы¶
- Убирает условную логику "это лист или группа?" из клиентского кода.
- Упрощает добавление новых типов узлов - они просто реализуют существующий интерфейс.
- Рекурсивная структура хорошо ложится на рекурсивные алгоритмы (подсчёт суммы, поиск, сериализация).
Минусы¶
- Общий интерфейс иногда вынуждает добавлять в
Leafметоды, которые ему на самом деле не нужны (например,Add(child)для листа без потомков) - либо такие методы бросают исключение, либо интерфейс приходится делить на более узкие части. - Если операции над листьями и составными узлами принципиально разные, единый интерфейс превращается в искусственную абстракцию, которая усложняет код больше, чем упрощает.
Пример в .NET Framework / BCL¶
System.Windows.Controls(WPF) иSystem.Windows.Forms.Control- элементы управления образуют дерево, где контейнер (Panel,GroupBox) хранит другие контролы, а операции вродеRender/Layoutрекурсивно применяются ко всему поддереву.System.Linq.Expressions.Expression- дерево выражений, где узлы (BinaryExpression,MethodCallExpression, ...) могут содержать другие узлы того же базового типаExpression.XElement/XmlNode- DOM-подобные деревья, где узел документа может содержать другие узлы.