Reg.ru: домены и хостинг

Крупнейший регистратор и хостинг-провайдер в России.

Более 2 миллионов доменных имен на обслуживании.

Продвижение, почта для домена, решения для бизнеса.

Более 700 тыс. клиентов по всему миру уже сделали свой выбор.

Перейти на сайт->

Бесплатный Курс "Практика HTML5 и CSS3"

Освойте бесплатно пошаговый видеокурс

по основам адаптивной верстки

на HTML5 и CSS3 с полного нуля.

Начать->

Фреймворк Bootstrap: быстрая адаптивная вёрстка

Пошаговый видеокурс по основам адаптивной верстки в фреймворке Bootstrap.

Научитесь верстать просто, быстро и качественно, используя мощный и практичный инструмент.

Верстайте на заказ и получайте деньги.

Получить в подарок->

Бесплатный курс "Сайт на WordPress"

Хотите освоить CMS WordPress?

Получите уроки по дизайну и верстке сайта на WordPress.

Научитесь работать с темами и нарезать макет.

Бесплатный видеокурс по рисованию дизайна сайта, его верстке и установке на CMS WordPress!

Получить в подарок->

*Наведите курсор мыши для приостановки прокрутки.


Шаблон проектирования «Декоратор»

В серии статей про шаблоны проектирования мы уже разобрали шаблоны "Фасад" и "Адаптер". С помощью "Фасада" мы можем упростить большие системы, а за счет внедрения "Адаптера" мы упрощаем себе жизнь, работая с внешними 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();

// Результат
Добавленный контент для Нового Года.
Добавленный контент для Рождества.
Это тело обычного письма.

Итого, мы осуществили изменения в теле письма без затрагивания кода базового класса.

Вывод

Практически каждое приложение требует каких-то изменений или улучшений в определенное время. В таких ситуациях мы можем использовать шаблон проектирования "Декоратор", который, в конечном счете, улучшает наш код и делает его более расширяемым.

Понравился материал и хотите отблагодарить?
Просто поделитесь с друзьями и коллегами!


Смотрите также:

PHP: Получение информации об объекте или классе, методах, свойствах и наследовании

PHP: Получение информации об объекте или классе, методах, свойствах и наследовании

CodeIgniter: жив или мертв?

CodeIgniter: жив или мертв?

Функции обратного вызова, анонимные функции и механизм замыканий

Функции обратного вызова, анонимные функции и механизм замыканий

Применение функции к каждому элементу массива

Применение функции к каждому элементу массива

Слияние массивов. Преобразование массива в строку

Слияние массивов. Преобразование массива в строку

Деструктор и копирование объектов с помощью метода __clone()

Деструктор и копирование объектов с помощью метода __clone()

Эволюция веб-разработчика или Почему фреймворк - это хорошо?

Эволюция веб-разработчика или Почему фреймворк - это хорошо?

Магические методы в PHP или методы-перехватчики (сеттеры, геттеры и др.)

Магические методы в PHP или методы-перехватчики (сеттеры, геттеры и др.)

PHP: Удаление элементов массива

PHP: Удаление элементов массива

Ключевое слово final (завершенные классы и методы в PHP)

Ключевое слово final (завершенные классы и методы в PHP)

50 классных сервисов, программ и сайтов для веб-разработчиков

50 классных сервисов, программ и сайтов для веб-разработчиков

Наверх