Паттерн «Декоратор»
Часть раздела Объектно-ориентированное программирование путешествия по 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 для каждой возможной комбинации. Нужно двойное молоко? Просто оберните его дважды.
Задание
ЛегкоДавайте создадим систему заказа пиццы с использованием паттерна 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.50PepperoniTopping: добавляет к описанию", Pepperoni", а к цене —2.00MushroomTopping: добавляет к описанию", 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()
?>В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово $thisМетодыСвойстваКонструктор (__construct)Деструктор (__destruct)Повторение — простой калькулятор4Наследование
Основы наследованияКлючевое слово parent::Переопределение методовКлючевое слово finalАбстрактные классыПовторение — иерархия сотрудников7Инкапсуляция
Public, Protected, PrivateМодификаторы доступа подробноГеттеры и сеттерыСокрытие информацииПродвижение конструктора (8.0)Повторение — система учёта студентов10Продвинутые концепции ООП
Композиция и наследованиеВнедрение зависимостейАнонимные классыПеречисления (PHP 8.1)Файберы (PHP 8.1)Глубокое погружение в клонирование объектовГенераторы и итераторы13Паттерны проектирования. Часть 2
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Репозиторий»2Пространства имён и автозагрузка
Введение в пространства имёнКлючевое слово useСтандарт автозагрузки PSR-4Автозагрузчик ComposerПовторение — структурированный проект5Интерфейсы и контракты
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовИнтерфейс и абстрактный классАннотация типов с интерфейсамиПовторение — калькулятор фигур8Магические методы
Введение в магические методы__toString и __debugInfo__get, __set, __isset, __unset__call и __callStatic__clone и клонирование объектов__serialize и __unserializeПовторение — пользовательская коллекция3Свойства классов
Свойства экземпляра и статические свойстваКонстанты в классахСтатические методы и свойстваПриватные и защищённые свойстваСвойства только для чтения (PHP 8.1)Повторение — менеджер банковского счёта6Полиморфизм
Повторение переопределения методовПолиморфизм через интерфейсыАннотации типов и объединённые типыПозднее статическое связываниеПовторение — обработчик платежей9Трейты
Введение в трейтыИспользование нескольких трейтовРазрешение конфликтов трейтовАбстрактные методы в трейтахТрейты и наследование12Паттерны проектирования, часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн StrategyПотренируйтесь самостоятельно: Онлайн-компилятор PHP