
Reg.ru: домены и хостинг
Крупнейший регистратор и хостинг-провайдер в России.
Более 2 миллионов доменных имен на обслуживании.
Продвижение, почта для домена, решения для бизнеса.
Более 700 тыс. клиентов по всему миру уже сделали свой выбор.

Бесплатный Курс "Практика HTML5 и CSS3"
Освойте бесплатно пошаговый видеокурс
по основам адаптивной верстки
на HTML5 и CSS3 с полного нуля.

Фреймворк Bootstrap: быстрая адаптивная вёрстка
Пошаговый видеокурс по основам адаптивной верстки в фреймворке Bootstrap.
Научитесь верстать просто, быстро и качественно, используя мощный и практичный инструмент.
Верстайте на заказ и получайте деньги.
*Наведите курсор мыши для приостановки прокрутки.
Шаблон проектирования «Декоратор»
В серии статей про шаблоны проектирования мы уже разобрали шаблоны "Фасад" и "Адаптер". С помощью "Фасада" мы можем упростить большие системы, а за счет внедрения "Адаптера" мы упрощаем себе жизнь, работая с внешними API и классами.
Сейчас же мы рассмотрим шаблон под названием "Декоратор", относящийся к группе структурных.
Данный шаблон проектирования мы можем использовать в том случае, когда мы хотим добавить некой функциональности базовому классу. Это отличная альтернатива подходу с созданием дополнительных подклассов для расширения функций класса.
Проблема
Если вы не совсем согласны и думаете, что сможете добиться того же самого просто расширяя базовый класс, то давайте посмотрим на примеры кода, которые, возможно, изменят ваш взгляд и заставят полюбить шаблон "Декоратор".
Возьмем в качестве примера класс, который отвечает за генерацию контента для электронного письма. В коде ниже вы можете видеть пример, который прекрасно справляется со своей задачей, если не требуется изменять параметры письма и его содержимое.
class eMailBody { private $header = 'Это хедер письма'; private $footer = 'Это футер письма'; public $body = ''; public function loadBody() { $this->body .= "Это тело обычного письма.<br />"; } }
Но на носу Рождество и, давайте представим, что я хочу в следующем письме поздравить с этим делом своих читателей. Для этого нужно в тело письма добавить соответствующий текст и подходящую картинку.
Чтобы осуществить это, я могу напрямую отредактировать базовый класс, но это то, чего я делать категорически не хочу. Поэтому я могу организовать наследование и добиться нужного результата. Я создал отдельный дочерний класс для базового класса отправки email:
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(); // Результат Добавленный контент для Нового Года. Добавленный контент для Рождества. Это тело обычного письма.
Итого, мы осуществили изменения в теле письма без затрагивания кода базового класса.
Вывод
Практически каждое приложение требует каких-то изменений или улучшений в определенное время. В таких ситуациях мы можем использовать шаблон проектирования "Декоратор", который, в конечном счете, улучшает наш код и делает его более расширяемым.
Понравился материал и хотите отблагодарить?
Просто поделитесь с друзьями и коллегами!
Смотрите также: