Паттерн Компоновщик
Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy — урок 80 из 91.
Паттерн Компоновщик (Composite Pattern) — это структурный паттерн проектирования, который позволяет группировать объекты в древовидные структуры и работать с этими структурами так, как если бы они были отдельными объектами. Он идеально подходит для представления иерархий «часть-целое», где вы хотите единообразно обрабатывать как одиночные объекты, так и группы объектов.
Рассмотрим файловую систему. Папка может содержать файлы и другие папки.
Когда вы вычисляете общий размер, вы хотите вызывать один и тот же метод как для файлов, так и для папок, при этом папки должны рекурсивно суммировать свое содержимое. Паттерн Компоновщик (Composite Pattern) делает этот процесс бесшовным, предоставляя как листьям (файлам), так и составным объектам (папкам) один и тот же интерфейс.
<?php
interface FileSystemItem {
public function getName(): string;
public function getSize(): int;
}
class File implements FileSystemItem {
public function __construct(
private string $name,
private int $size
) {}
public function getName(): string {
return $this->name;
}
public function getSize(): int {
return $this->size;
}
}
class Folder implements FileSystemItem {
private array $items = [];
public function __construct(private string $name) {}
public function getName(): string {
return $this->name;
}
public function add(FileSystemItem $item): void {
$this->items[] = $item;
}
public function getSize(): int {
$total = 0;
foreach ($this->items as $item) {
$total += $item->getSize();
}
return $total;
}
}
Теперь вы можете создавать древовидные структуры и работать с ними единообразно:
<?php
$docs = new Folder("Documents");
$docs->add(new File("resume.pdf", 250));
$docs->add(new File("cover.docx", 50));
$images = new Folder("Images");
$images->add(new File("photo.jpg", 1500));
$root = new Folder("Root");
$root->add($docs);
$root->add($images);
$root->add(new File("readme.txt", 10));
echo $root->getSize();
Вывод:
1810Клиентский код вызывает getSize(), не заботясь о том, имеет ли он дело с отдельным файлом или глубоко вложенной структурой папок. Паттерн обрабатывает рекурсию внутри себя, что упрощает работу со сложными иерархиями.
Задание
ЛегкоДавайте создадим систему организационной структуры, используя паттерн Компоновщик (Composite). Компании имеют иерархическую структуру, где отделы могут содержать сотрудников и другие подотделы — это идеальная древовидная структура, в которой вы хотите единообразно рассчитывать общие показатели по всей организации.
Вы распределите свой код по четырем файлам:
OrganizationUnit.php— Определите интерфейсOrganizationUnit, который будут реализовывать как отдельные сотрудники, так и отделы. Он должен иметь два метода:getName(): stringиgetSalary(): int. Этот общий интерфейс позволяет вам одинаково относиться к отдельным сотрудникам и целым отделам при расчете затрат.Employee.php— Подключите интерфейс OrganizationUnit и создайте классEmployee, который его реализует. Сотрудник является листовым узлом в дереве вашей организации — у него есть имя и зарплата, но он не может содержать другие элементы. Используйте продвижение свойств в конструкторе (constructor promotion) для принятия имени и зарплаты и реализуйте оба метода интерфейса.Department.php— Подключите интерфейс OrganizationUnit и создайте классDepartment, который его реализует. Отдел — это компоновщик, который может содержать как сотрудников, так и другие отделы. Ваш отдел должен:- Принимать имя в конструкторе
- Иметь метод
add(OrganizationUnit $unit): voidдля добавления сотрудников или подотделов - Реализовать
getSalary()для возврата общей зарплаты всех содержащихся в нем единиц (рекурсивно суммируя все внутри)
main.php— Подключите файлы Employee и Department. Вы получите один входной параметр: строку JSON, представляющую структуру организации.JSON будет в следующем формате:
{"Engineering": [{"name": "Alice", "salary": 80000}, {"name": "Bob", "salary": 75000}], "Sales": [{"name": "Carol", "salary": 65000}]}Каждый ключ — это название отдела, а его значение — массив сотрудников с их именами и зарплатами.
Создайте корневой
Departmentс названием"Company". Для каждого отдела в JSON создайтеDepartment, добавьте в него сотрудников, а затем добавьте этот отдел в Company. Наконец, выведите общую зарплату компании в следующем формате:Total Salary: [amount]
Прелесть паттерна Компоновщик заключается в том, что вызов getSalary() для отдела Company автоматически вычисляет сумму по всем вложенным отделам и сотрудникам — клиентскому коду не нужно знать внутреннюю структуру.
Шпаргалка
Паттерн Компоновщик (Composite Pattern) — это структурный паттерн проектирования, который объединяет объекты в древовидные структуры и позволяет работать с отдельными объектами и группами объектов единообразно.
Он идеально подходит для иерархий «часть-целое», где требуется одинаковый интерфейс как для листовых узлов, так и для составных узлов.
Основные компоненты
- Интерфейс компонента (Component Interface) — определяет общие операции как для листьев, так и для компоновщиков
- Лист (Leaf) — представляет собой отдельные объекты, которые не могут содержать другие объекты
- Компоновщик (Composite) — содержит дочерние элементы (листья или другие компоновщики) и реализует операции, делегируя их выполнение дочерним элементам
Пример: Файловая система
<?php
interface FileSystemItem {
public function getName(): string;
public function getSize(): int;
}
class File implements FileSystemItem {
public function __construct(
private string $name,
private int $size
) {}
public function getName(): string {
return $this->name;
}
public function getSize(): int {
return $this->size;
}
}
class Folder implements FileSystemItem {
private array $items = [];
public function __construct(private string $name) {}
public function getName(): string {
return $this->name;
}
public function add(FileSystemItem $item): void {
$this->items[] = $item;
}
public function getSize(): int {
$total = 0;
foreach ($this->items as $item) {
$total += $item->getSize();
}
return $total;
}
}
Использование
<?php
$docs = new Folder("Documents");
$docs->add(new File("resume.pdf", 250));
$docs->add(new File("cover.docx", 50));
$images = new Folder("Images");
$images->add(new File("photo.jpg", 1500));
$root = new Folder("Root");
$root->add($docs);
$root->add($images);
$root->add(new File("readme.txt", 10));
echo $root->getSize(); // 1810
Клиент вызывает getSize() единообразно как для файлов, так и для папок. Компоновщик обрабатывает рекурсию внутри себя, что упрощает работу со сложными иерархиями.
Попробуйте сами
<?php
require_once 'Employee.php';
require_once 'Department.php';
// Чтение входных данных JSON
$input = trim(fgets(STDIN));
$data = (array)json_decode($input, true);
// TODO: Создать корневой отдел под названием "Company"
// TODO: Пройти циклом по каждому отделу в данных JSON
// Для каждого отдела:
// - Создать новый объект Department
// - Добавить каждого сотрудника в этот отдел
// - Добавить отдел в Company
// TODO: Вывести общую зарплату в формате: Total Salary: [amount]
?>В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово $thisМетодыСвойстваКонструктор (__construct)Деструктор (__destruct)Итоги — Простой калькулятор4Наследование
Основы наследованияКлючевое слово parent::Переопределение методовКлючевое слово finalАбстрактные классыИтоги — Иерархия сотрудников7Инкапсуляция
Public, Protected, PrivateПодробный разбор модификаторов доступаГеттеры и сеттерыСокрытие данныхConstructor Promotion (8.0)Итоги — Система учета студентов10Продвинутые концепции ООП
Композиция против наследованияВнедрение зависимостейАнонимные классыПеречисления (PHP 8.1)Файберы (PHP 8.1)Глубокое погружение в клонирование объектовГенераторы и итераторы13Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий2Пространства имен и автозагрузка
Введение в пространства именКлючевое слово useСтандарт автозагрузки PSR-4Автозагрузчик ComposerИтоги: структурированный проект5Интерфейсы и контракты
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовИнтерфейс против абстрактного классаType Hinting с интерфейсамиИтоги — Калькулятор фигур8Магические методы
Введение в магические методы__toString и __debugInfo__get, __set, __isset, __unset__call и __callStatic__clone и клонирование объектов__serialize и __unserializeИтоги — Кастомная коллекция11Система типов и обработка ошибок
Объявление типовNullable-типыТипы объединения и пересеченияКлассы исключенийИерархия пользовательских исключенийTry, Catch, FinallyИтоги — Валидатор форм14Проект: Управление библиотекой
Обзор проектаКлассы Book и User3Свойства классов
Свойства экземпляра и статические свойстваКонстанты в классахСтатические методы и свойстваПриватные и защищенные свойстваСвойства Readonly (PHP 8.1)Итоги — Менеджер банковского счета6Полиморфизм
Снова о переопределении методовПолиморфизм через интерфейсыType Hinting и Union TypesПозднее статическое связываниеИтоги — Payment Processor9Трейты
Введение в трейтыИспользование нескольких трейтовРазрешение конфликтов трейтовАбстрактные методы в трейтахТрейты и наследование12Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy