Composite
Паттерн для древовидных структур: с отдельным объектом и группой объектов работаем одинаково.
Composite компонует объекты в древовидную структуру и позволяет клиенту работать с одиночными объектами и их группами через общий интерфейс.
Какую задачу решает
Папка содержит файлы и другие папки. Меню содержит пункты и подменю. У таких структур есть «листья» и «контейнеры», и хочется обращаться с ними единообразно: попросить размер у файла и у целой папки одинаковым вызовом. Composite даёт обоим общий интерфейс, а контейнер рекурсивно опрашивает детей.
Идея и реализация
from abc import ABC, abstractmethod
class Node(ABC):
@abstractmethod
def size(self): ...
class File(Node): # лист
def __init__(self, name, size):
self.name = name
self._size = size
def size(self):
return self._size
class Folder(Node): # контейнер
def __init__(self, name):
self.name = name
self.children = []
def add(self, node):
self.children.append(node)
return self
def size(self):
return sum(child.size() for child in self.children)
root = Folder("root")
root.add(File("a.txt", 100)).add(File("b.txt", 250))
sub = Folder("sub")
sub.add(File("c.txt", 50))
root.add(sub)
print("Размер root:", root.size())
Вывод:
Размер root: 400
Метод size() у файла возвращает своё число, а у папки — рекурсивно суммирует детей, не зная, файлы это или вложенные папки. Клиент вызывает root.size() и получает 400 (100 + 250 + 50). В этом сила паттерна: рекурсия скрыта, интерфейс общий.
Обратите внимание, что добавление новой операции (например, count_files() или render()) сводится к одному методу в общем интерфейсе, который лист и контейнер реализуют по-своему. А любая операция над всем деревом пишется без единого isinstance: вы просто вызываете её на корне, и она «протекает» вниз по структуре. Именно отказ от проверок типа делает код с Composite чистым и расширяемым.
Тонкий вопрос проектирования — где объявлять метод add. Положить его в общий интерфейс удобно (клиент работает единообразно), но тогда лист вынужден как-то реагировать на бессмысленный для него вызов. Альтернатива — держать add только в контейнере; тогда клиент иногда должен различать узлы. Это классический компромисс Composite между «прозрачностью» и «безопасностью»; единственно верного ответа нет, выбирайте по контексту.
Плюсы и минусы
- Плюс: единообразная работа с деревом любой глубины; новые типы узлов добавляются легко.
- Минус: общий интерфейс может стать слишком общим (метод
addбессмыслен для листа).
Где встречается
Файловые системы, DOM-дерево HTML, дерево UI-компонентов (виджет содержит виджеты), AST в компиляторах, графические редакторы (группа фигур ведёт себя как фигура).
Итог
- Composite — для рекурсивных древовидных структур.
- Лист и контейнер реализуют общий интерфейс; контейнер делегирует детям.
- Клиент не различает одиночный объект и группу.