Menu
Coddy logo textTech

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

Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy. Урок 80 из 91.

Composite Pattern — это структурный шаблон проектирования, который позволяет объединять объекты в древовидные структуры, а затем работать с этими структурами так, как если бы они были отдельными объектами. Он идеально подходит для представления иерархий «часть — целое», в которых вы хотите единообразно обрабатывать отдельные объекты и группы объектов.

Рассмотрим файловую систему. folder can contain файлы и другие folder.

При вычислении общего размера нужно вызвать один и тот же метод и для файлов, и для папок, а папки должны рекурсивно суммировать своё содержимое. Паттерн Composite упрощает эту задачу, предоставляя и листьям (файлам), и composites (папкам) один и тот же интерфейс.

<?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(), не заботясь о том, работает ли он с одним file или со структурой глубоко вложенных folder. Паттерн обрабатывает рекурсию внутри себя, благодаря чему со сложными иерархиями легко работать.

challenge icon

Задание

Легко

Давайте создадим систему организационной структуры с использованием шаблона Composite. В компаниях иерархическая структура, где department может содержать employees и другие sub-departments: идеальная древовидная структура, в которой нужно единообразно вычислять итоговые значения для всей организации.

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

  • OrganizationUnit.php: Определите интерфейс OrganizationUnit, который будут реализовывать как отдельные employees, так и departments. В нем должны быть два метода: getName(): string и getSalary(): int. Этот общий интерфейс позволяет одинаково обрабатывать отдельных employees и целые departments при вычислении затрат.
  • Employee.php: Подключите интерфейс OrganizationUnit и создайте Employee class, который его реализует. Employee — это конечный узел в дереве организации. У него есть имя и зарплата, но он cannot содержать другие units. Используйте продвижение параметров конструктора, чтобы принять имя и зарплату, и реализуйте оба метода интерфейса.
  • Department.php: Подключите интерфейс OrganizationUnit и создайте Department class, который его реализует. Department — это composite, который can содержать both employees и другие departments. Ваш department должен:
    • Принимать имя в своем конструкторе
    • Реализовать getName(), чтобы возвращать имя department
    • Иметь метод add(OrganizationUnit $unit): void для добавления employees или sub-departments
    • Реализовать getSalary(), чтобы возвращать общую зарплату всех содержащихся в нем units (рекурсивно суммируя все элементы внутри)
  • main.php: Подключите файлы Employee и Department. Вы получите один входной параметр: JSON-строку, представляющую структуру организации.

    JSON будет иметь следующий формат:

    {"Engineering": [{"name": "Alice", "salary": 80000}, {"name": "Bob", "salary": 75000}], "Sales": [{"name": "Carol", "salary": 65000}]}

    Каждый ключ — это название department, а его значение — array employees с их именами и зарплатами.

    Создайте корневой Department с именем "Company". Для каждого department в JSON создайте Department, добавьте его employees, а затем добавьте этот department в Company. Наконец, выведите общую зарплату компании в следующем формате:

    Total Salary: [amount]

Преимущество шаблона Composite заключается в том, что вызов getSalary() для department Company автоматически вычисляет сумму по всем вложенным departments и employees: клиентскому коду не нужно знать внутреннюю структуру.

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

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

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

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

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

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

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

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

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

Потренируйтесь самостоятельно: Онлайн-компилятор PHP