Что это такое
Паттерн Компоновщик (Composite) работает с отдельными вещами и с группами вещей через один и тот же интерфейс. У файла есть размер; у папки, полной файлов (и папок), размер тоже есть — поэтому оба реализуют size(), и код, который складывает размеры, никогда не спрашивает, что именно у него в руках.
Компоновщики естественно образуют деревья: группы содержат листья и другие группы, на любую глубину.
Пример: файлы и папки
В интерфейсе Item один метод. Файл отвечает своим собственным размером; папка спрашивает всё, что внутри, и складывает — рекурсия получается сама собой.
Один интерфейс для листа и для группы
$root->size() обходит всё дерево без единой проверки типа: папке всё равно, является ли элемент файлом на 4 КБ или другой папкой, в которой спрятано ещё десять.
Вывод: PHP-скрипт
<?php
interface Item {
public function size(): int;
}
class File implements Item {
public function __construct(
private string $name,
private int $size,
) {}
public function size(): int {
return $this->size;
}
}
class Folder implements Item {
public function __construct(
private string $name,
private array $items,
) {}
// Размер папки — это сумма всего, что внутри: файлов или папок.
public function size(): int {
$total = 0;
foreach ($this->items as $item) {
$total += $item->size();
}
return $total;
}
}
$docs = new Folder("docs", [
new File("notes.txt", 4),
new File("plan.md", 6),
]);
$root = new Folder("root", [
new File("readme.md", 2),
$docs,
]);
echo "размер docs: ", $docs->size(), " КБ\n";
echo "размер root: ", $root->size(), " КБ\n";
Когда его применять
Применяйте для данных в форме дерева, где группы и отдельные элементы отвечают на одни и те же вопросы — файловые системы, меню с подменю, раскладки интерфейса, организационные схемы.
Не применяйте для плоских списков. Массиву с циклом никакой паттерн не нужен.
Плюсы и минусы
Плюсы: клиентский код остаётся плоским и простым; деревья любой глубины получаются бесплатно; новые классы листьев или групп подключаются через тот же интерфейс.
Минусы: общий интерфейс может навязывать листьям неуместные методы (что такое «добавить потомка» для файла?); слишком общие компоновщики трудно ограничить.