Шаблон проектирования «Декоратор»
В серии статей про шаблоны проектирования мы уже разобрали шаблоны "Фасад" и "Адаптер". С помощью "Фасада" мы можем упростить большие системы, а за счет внедрения "Адаптера" мы упрощаем себе жизнь, работая с внешними API и классами.
Сейчас же мы рассмотрим шаблон под названием "Декоратор", относящийся к группе структурных.
Данный шаблон проектирования мы можем использовать в том случае, когда мы хотим добавить некой функциональности базовому классу. Это отличная альтернатива подходу с созданием дополнительных подклассов для расширения функций класса.
Проблема
Если вы не совсем согласны и думаете, что сможете добиться того же самого просто расширяя базовый класс, то давайте посмотрим на примеры кода, которые, возможно, изменят ваш взгляд и заставят полюбить шаблон "Декоратор".
Возьмем в качестве примера класс, который отвечает за генерацию контента для электронного письма. В коде ниже вы можете видеть пример, который прекрасно справляется со своей задачей, если не требуется изменять параметры письма и его содержимое.
class eMailBody {
private $header = 'Это хедер письма';
private $footer = 'Это футер письма';
public $body = '';
public function loadBody() {
$this->body .= "Это тело обычного письма.<br />";
}
}
Но на носу Рождество и, давайте представим, что я хочу в следующем письме поздравить с этим делом своих читателей. Для этого нужно в тело письма добавить соответствующий текст и подходящую картинку.
Чтобы осуществить это, я могу напрямую отредактировать базовый класс, но это то, чего я делать категорически не хочу. Поэтому я могу организовать наследование и добиться нужного результата. Я создал отдельный дочерний класс для базового класса отправки email:
— Регулярная проверка качества ссылок по более чем 100 показателям и ежедневный пересчет показателей качества проекта.
— Все известные форматы ссылок: арендные ссылки, вечные ссылки, публикации (упоминания, мнения, отзывы, статьи, пресс-релизы).
— SeoHammer покажет, где рост или падение, а также запросы, на которые нужно обратить внимание.
SeoHammer еще предоставляет технологию Буст, она ускоряет продвижение в десятки раз, а первые результаты появляются уже в течение первых 7 дней. Зарегистрироваться и Начать продвижение
class christmasEmail extends eMailBody {
public function loadBody() {
parent::loadBody();
$this->body .= "Добавленный контент для Рождества<br />";
}
}
$christmasEmail = new christmasEmail();
$christmasEmail->loadBody();
echo $christmasEmail->body;
Ок, готово. Теперь пройдет еще несколько дней, и мне понадобится отправить email с поздравлениями с Новым Годом. Можно использовать тот же подход, что и с рождественским письмом.
class newYearEmail extends eMailBody {
public function loadBody() {
parent::loadBody();
$this->body .= "Добавленный контент для Нового Года<br />";
}
}
$newYearEmail = new newYearEmail();
$newYearEmail->loadBody();
echo $newYearEmail->body;
Ну что ж, все прошло гладко и без эксцессов. Теперь представим, что я оба раза забыл поздравить свои читателей (пропустил и Рождество, и Новый Год), и теперь хочу наверстать упущенное и поздравить их сразу с двумя прошедшими праздниками в одном письме без того, чтобы как-то изменять код в базовом классе.
Ваш мозг сразу начинает задаваться примерно таким вопросом: можно ли будет это как-то реализовать с помощью подклассов и наследования?
Было бы неплохо поступить именно так, однако это приведет к созданию дополнительного / ненужного кода. Мы же можем сделать нечто, что позволит нам реализовать механизм, напоминающий множественное наследование.
Решение
Как вы уже догадались, в дело вступает шаблон "Декоратор".
Википедия говорит нам следующее:
"Шаблон проектирования "Декоратор" позволяет реализовать дополнительные функции для конкретного объекта класса (как статически, так и динамически), не влияя при этом на поведение других объектов того же класса."
В примере выше мы видели, что можно расширить функционал, используя один подкласс, но, когда речь встает о добавлении нескольких функции или вариантов поведения, такой подход становится достаточно долгим и запутанным. И это тот случай, когда можно эффективно использовать шаблон "Декоратор".
Интерфейс
interface eMailBody {
public function loadBody();
}
Это простой интерфейс, позволяющий убедиться, что некоторый класс должен реализовывать необходимые методы.
Базовый класс
class eMail implements eMailBody {
public function loadBody() {
echo "Это тело обычного письма.<br />";
}
}
Это базовый класс, генерирующий тело обычного email-письма, которое уходит читателям. То, что нам нужно сейчас - это изменить контент письма в зависимости от определенных условий, не меняя при этом сам базовый класс.
Главный "Декоратор"
abstract class emailBodyDecorator implements eMailBody {
protected $emailBody;
public function __construct(eMailBody $emailBody) {
$this->emailBody = $emailBody;
}
abstract public function loadBody();
}
Это главный класс-декоратор, который содержит ссылку на базовый класс и меняет его поведение так, как нам нужно. В нем мы определили один абстрактный метод loadBody(), который должен быть реализован в суб-декораторе для изменения поведения.
Суб-"Декоратор"
class christmasEmailBody extends emailBodyDecorator {
public function loadBody() {
echo 'Добавленный контент для Рождества<br />';
$this->emailBody->loadBody();
}
}
class newYearEmailBody extends emailBodyDecorator {
public function loadBody() {
echo 'Добавленный контент для Нового Года.<br />';
$this->emailBody->loadBody();
}
}
Здесь мы создали два подкласса Главного "Декоратора", который, фактически и производит изменения поведения для нашего базового класса.
Собираем воедино
Мы создали все необходимые элементы. Теперь посмотрим, как будет выглядеть код целиком и насладимся плодами нашего труда:)
interface eMailBody {
public function loadBody();
}
class eMail implements eMailBody {
public function loadBody() {
echo "Это тело обычного письма.<br />";
}
}
abstract class emailBodyDecorator implements eMailBody {
protected $emailBody;
public function __construct(eMailBody $emailBody) {
$this->emailBody = $emailBody;
}
abstract public function loadBody();
}
class christmasEmailBody extends emailBodyDecorator {
public function loadBody() {
echo 'Добавленный контент для Рождества<br />';
$this->emailBody->loadBody();
}
}
class newYearEmailBody extends emailBodyDecorator {
public function loadBody() {
echo 'Добавленный контент для Нового Года.<br />';
$this->emailBody->loadBody();
}
}
Теперь используем наш класс-декоратор в "боевых" условиях:
— Разгрузит мастера, специалиста или компанию;
— Позволит гибко управлять расписанием и загрузкой;
— Разошлет оповещения о новых услугах или акциях;
— Позволит принять оплату на карту/кошелек/счет;
— Позволит записываться на групповые и персональные посещения;
— Поможет получить от клиента отзывы о визите к вам;
— Включает в себя сервис чаевых.
Для новых пользователей первый месяц бесплатно. Зарегистрироваться в сервисе
/* * Обычный Email */ $email = new eMail(); $email->loadBody(); // Результат Это тело обычного письма. /* * Email с Рождественским поздравлением */ $email = new eMail(); $email = new christmasEmailBody($email); $email->loadBody(); // Результат Добавленный контент для Рождества Это тело обычного письма. /* * Email с новогодним поздравлением */ $email = new eMail(); $email = new newYearEmailBody($email); $email->loadBody(); // Результат Добавленный контент для Нового Года. Это тело обычного письма. /* * Email с Рождественским и новогодним поздравлениями */ $email = new eMail(); $email = new christmasEmailBody($email); $email = new newYearEmailBody($email); $email->loadBody(); // Результат Добавленный контент для Нового Года. Добавленный контент для Рождества. Это тело обычного письма.
Итого, мы осуществили изменения в теле письма без затрагивания кода базового класса.
Вывод
Практически каждое приложение требует каких-то изменений или улучшений в определенное время. В таких ситуациях мы можем использовать шаблон проектирования "Декоратор", который, в конечном счете, улучшает наш код и делает его более расширяемым.
Понравился материал и хотите отблагодарить?
Просто поделитесь с друзьями и коллегами!
Смотрите также:

















