Menu
Coddy logo textTech

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

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

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

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

Each добавление оборачивает исходный coffee и adds к его description и стоимости. Ключевая идея заключается в том, что и base coffee, и декораторы используют один и тот же interface.

<?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;
    }
}

Декораторы реализуют тот же interface и оборачивают другой объект 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();

Output:

Simple Coffee, Milk, Sugar = $2.75

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

challenge icon

Задание

Легко

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

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

  • Pizza.php: Define интерфейс Pizza, который устанавливает контракт для всех пицц и топпингов. Он должен содержать два метода: getDescription(): string и getPrice(): float.
  • BasePizza.php: Подключите интерфейс Pizza и создайте класс MargheritaPizza, который его реализует. Это ваша базовая пицца с описанием "Margherita" и ценой 8.00.
  • Toppings.php: Подключите интерфейс Pizza и создайте три класса-декоратора, которые его реализуют. Каждый декоратор оборачивает объект Pizza (принимая его в constructor) и дополняет как описание, так и цену:
    • 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. Именно эта гибкость является преимуществом паттерна Decorator!

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

<?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Проверьте себя

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

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

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