Что это такое
Паттерн Компоновщик (Composite) работает с отдельными элементами и группами элементов через один и тот же интерфейс. У файла есть размер; у папки, полной файлов (и папок), тоже есть размер — поэтому оба реализуют size(), и коду, который суммирует размеры, не нужно выяснять, с чем именно он работает.
Компоновщики естественным образом образуют деревья: группы содержат листья и другие группы на любой глубине.
Пример: файлы и папки
У протокола Item всего один метод. Файл возвращает свой собственный размер; папка опрашивает всё, что в ней лежит, и складывает результаты — рекурсия получается сама собой.
Один интерфейс для листа и группы
root.size() обходит всё дерево без единой проверки типа: папке всё равно, является ли элемент файлом размером 4 KB или другой папкой, в которой спрятано ещё десять.
Вывод: полная программа на Python
from dataclasses import dataclass, field
from typing import Protocol
class Item(Protocol):
def size(self) -> int: ...
@dataclass
class File:
name: str
kb: int
def size(self) -> int:
return self.kb
@dataclass
class Folder:
name: str
items: list[Item] = field(default_factory=list)
# Размер папки — это сумма всего, что в ней лежит: файлов или папок.
def size(self) -> int:
return sum(item.size() for item in self.items)
docs = Folder("docs", [
File("notes.txt", 4),
File("plan.md", 6),
])
root = Folder("root", [
File("readme.md", 2),
docs,
])
print("Размер docs:", docs.size(), "KB")
print("Размер root:", root.size(), "KB")
Когда использовать
Используйте его для древовидных данных, где группы и отдельные элементы отвечают на одни и те же вопросы: файловые системы, меню с подменю, макеты интерфейса, организационные структуры.
Не используйте его для плоских списков. Списку с циклом никакой паттерн не нужен.
Плюсы и минусы
Плюсы: клиентский код остаётся плоским и простым; деревья любой глубины получаются сами собой; новые классы листьев или групп подключаются к тому же протоколу.
Минусы: общий протокол может навязывать листьям неудобные методы (что значит «добавить дочерний элемент» для файла?); слишком универсальные компоновщики трудно ограничить.