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