Menu
Coddy logo textTech

Паттерн Компоновщик

Часть раздела Объектно-ориентированное программирование путешествия по 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(), не заботясь о том, имеет ли он дело с отдельным файлом или глубоко вложенной структурой папок. Паттерн обрабатывает рекурсию внутри себя, что упрощает работу со сложными иерархиями.

challenge icon

Задание

Легко

Давайте создадим систему организационной структуры, используя паттерн Компоновщик (Composite Pattern). Компании имеют иерархическую структуру, где отделы могут содержать сотрудников и другие подотделы — это идеальная древовидная структура, в которой вы хотите единообразно рассчитывать итоговые показатели по всей организации.

Вы организуете свой код в четырех файлах:

  • OrganizationUnit.php — Определите интерфейс OrganizationUnit, который будут реализовывать как отдельные сотрудники, так и отделы. Он должен иметь два метода: getName(): string и getSalary(): int. Этот общий интерфейс позволяет вам одинаково относиться к отдельным сотрудникам и целым отделам при расчете затрат.
  • Employee.php — Подключите интерфейс OrganizationUnit и создайте класс Employee, который его реализует. Сотрудник является листовым узлом в дереве вашей организации — у него есть имя и зарплата, но он не может содержать другие подразделения. Используйте продвижение свойств в конструкторе (constructor promotion) для принятия имени и зарплаты и реализуйте оба метода интерфейса.
  • Department.php — Подключите интерфейс OrganizationUnit и создайте класс Department, который его реализует. Отдел — это компоновщик (composite), который может содержать как сотрудников, так и другие отделы. Ваш отдел должен:
    • Принимать имя в своем конструкторе
    • Реализовать getName() для возврата названия отдела
    • Иметь метод 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 автоматически вычисляет сумму по всем вложенным отделам и сотрудникам — клиентскому коду не нужно знать внутреннюю структуру.

Попробуйте сами

<?php
require_once 'Employee.php';
require_once 'Department.php';

// Прочитать входные данные JSON
$input = trim(fgets(STDIN));
$data = (array)json_decode($input, true);

// TODO: Создать корневой Department с названием "Company"

// TODO: Пройти циклом по каждому отделу в данных JSON
// Для каждого отдела:
//   - Создать новый объект Department
//   - Добавить каждого сотрудника в этот отдел
//   - Добавить отдел в Company

// TODO: Вывести общую зарплату в формате: Total Salary: [amount]

?>
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование