Menu
Coddy logo textTech

Паттерн Декоратор

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

Паттерн Декоратор — это структурный паттерн проектирования, который позволяет динамически добавлять объектам новые варианты поведения, оборачивая их в специальные объекты-обертки, называемые декораторами. В отличие от наследования, которое добавляет поведение на этапе компиляции, декораторы позволяют изменять поведение объектов во время выполнения.

Представьте себе кофейню. Вы начинаете с обычного кофе, а затем добавляете добавки, такие как молоко, сахар или взбитые сливки.

Каждое дополнение оборачивает исходный кофе и добавляет к его описанию и стоимости. Ключевая идея заключается в том, что и базовый кофе, и декораторы имеют один и тот же интерфейс.

<?php
interface Coffee {
    public function getDescription(): string;
    public function getCost(): float;
}

class SimpleCoffee implements Coffee {
    public function getDescription(): string {
        return "Simple Coffee";
    }
    
    public function getCost(): float {
        return 2.00;
    }
}

Декораторы реализуют тот же интерфейс и оборачивают другой Coffee объект:

<?php
class MilkDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Milk";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.50;
    }
}

class SugarDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Sugar";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.25;
    }
}

$coffee = new SimpleCoffee();
$coffee = new MilkDecorator($coffee);
$coffee = new SugarDecorator($coffee);

echo $coffee->getDescription() . " = $" . $coffee->getCost();

Вывод:

Simple Coffee, Milk, Sugar = $2.75

Каждый декоратор оборачивает предыдущий объект, формируя цепочку. Паттерн позволяет комбинировать поведения в любом порядке и количестве без создания класса для каждой возможной комбинации. Нужно двойное молоко? Просто оберните его дважды.

challenge icon

Задание

Легко

Давайте создадим систему заказа пиццы, используя паттерн Декоратор. Вы начнете с базовой пиццы и будете динамически добавлять топпинги — каждый топпинг оборачивает пиццу и дополняет ее описание и цену, точно так же, как в примере с кофе из урока.

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

  • Pizza.php — Определите интерфейс Pizza, который устанавливает контракт для всех пицц и топпингов. Он должен иметь два метода: getDescription(): string и getPrice(): float.
  • BasePizza.php — Подключите интерфейс Pizza и создайте класс MargheritaPizza, который его реализует. Это ваша базовая пицца с описанием "Margherita" и ценой 8.00.
  • Toppings.php — Подключите интерфейс Pizza и создайте три класса-декоратора, которые его реализуют. Каждый декоратор оборачивает объект Pizza (принимайте его в конструкторе) и дополняет как описание, так и цену:
    • CheeseTopping — Добавляет ", Extra Cheese" к описанию и 1.50 к цене
    • PepperoniTopping — Добавляет ", Pepperoni" к описанию и 2.00 к цене
    • MushroomTopping — Добавляет ", Mushrooms" к описанию и 1.25 к цене
  • main.php — Подключите файлы BasePizza и Toppings. Вы получите один входной параметр: список топпингов через запятую (например, "cheese,pepperoni" или "mushroom,cheese,cheese").

    Начните с MargheritaPizza. Для каждого топпинга из входных данных оберните вашу пиццу соответствующим декоратором:

    • "cheese" оборачивает в CheeseTopping
    • "pepperoni" оборачивает в PepperoniTopping
    • "mushroom" оборачивает в MushroomTopping

    После применения всех топпингов выведите описание и цену итоговой пиццы в следующем формате:

    [description] = $[price]

    Отформатируйте цену до двух знаков после запятой.

Обратите внимание, что один и тот же топпинг можно применять несколько раз — заказ двойного сыра просто означает двойное оборачивание в CheeseTopping. Эта гибкость и есть сила паттерна Декоратор!

Шпаргалка

Паттерн Декоратор — это структурный паттерн проектирования, который позволяет динамически добавлять объектам новое поведение, оборачивая их в объекты-декораторы во время выполнения кода.

Как базовый объект, так и декораторы реализуют один и тот же интерфейс:

<?php
interface Coffee {
    public function getDescription(): string;
    public function getCost(): float;
}

class SimpleCoffee implements Coffee {
    public function getDescription(): string {
        return "Simple Coffee";
    }
    
    public function getCost(): float {
        return 2.00;
    }
}

Декораторы оборачивают другой объект того же типа интерфейса:

<?php
class MilkDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Milk";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.50;
    }
}

class SugarDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Sugar";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.25;
    }
}

Объединяйте декораторы в цепочки, чтобы комбинировать поведение:

$coffee = new SimpleCoffee();
$coffee = new MilkDecorator($coffee);
$coffee = new SugarDecorator($coffee);

echo $coffee->getDescription() . " = $" . $coffee->getCost();
// Результат: Simple Coffee, Milk, Sugar = $2.75

Декораторы можно применять многократно и в любом порядке без необходимости создания отдельных классов для каждой возможной комбинации.

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

<?php
require_once 'BasePizza.php';
require_once 'Toppings.php';

// Чтение входных данных - список топпингов через запятую
$input = trim(fgets(STDIN));
$toppings = explode(',', $input);

// Начинаем с базовой пиццы MargheritaPizza
$pizza = new MargheritaPizza();

// TODO: Пройтись циклом по каждому топпингу и обернуть пиццу соответствующим декоратором
// - "cheese" -> обернуть в CheeseTopping
// - "pepperoni" -> обернуть в PepperoniTopping
// - "mushroom" -> обернуть в MushroomTopping

// TODO: Вывести результат в формате: [description] = $[price]
// Форматировать цену до 2 знаков после запятой с помощью number_format()

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

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

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