Паттерн «Декоратор»

Шаблон Декоратор в Ларавел

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

Этот паттерн часто используется как альтернатива подклассам. Декорирование может обеспечить новое поведение для объектов во время выполнения, в то время как подклассы добавляют поведение во время компиляции и влияют на все экземпляры исходного класса. Эта различие между декорированием и созданием подклассов становится еще более важной, когда существует множество независимых способов расширения функциональности.

Еще одной причиной, по которой часто используется Декоратора, является соблюдение принципа единой ответственности, поскольку он позволяет инкапсулировать функциональность в различные классы декоратора.

В этой статье я сделаю Декоратор для пекарни, чтобы покупатели могли сами приготовить торт, выбрав ингредиенты. Цена на торт будет рассчитываться на основе ингредиентов.

Теория паттерна Декоратор

Паттерн имеет один интерфейс, называемый Component, который реализуется двумя классами: ConcreteComponent и Decorator.

Класс Decorator имеет экземпляр Component, который может быть экземпляром ConcreteComponent или Decorator. Это позволит размещать несколько декораторов друг над другом, что добавляет новые функциональные возможности к переопределенным методам.

Последний тип класса, в шаблоне декоратора, это ConcreteDecorator. Это классы, которые декорируют Component, добавляя ему поведение.

Реализация

Прежде всего, давайте обновим UML-диаграмму для нашего примера.

Паттерн Декаратор в Ларавел

В этом примере интерфейс Cake имеет два метода: ingredients и price, которые возвращают ингредиенты и цену, соответственно.

interface Cake
{
    public function ingredients(): string;
  
    public function price(): double;
}

CakeDecorator — это абстрактный класс, который заставляет каждый конкретный декоратор, расширяющий CakeDecorator, реализовывать свои собственные версии ingredients и price. Обратите внимание, что CakeDecorator получает экземпляр Cake, который делает возможным создание цепочек.

abstract class CakeDecorator implements Cake
{
    /**
     * @var Cake
     */
    protected $cake;
    public function __construct(Cake $cake)
    {
        $this->cake = $cake;
    }
}

Класс SimpleCake является примером класса Component, который можно декорировать. Эта функциональность очень легко расширяема. Если пекарня хочет сделать дополнительную опцию для шоколадных тортов, все, что нужно, это создать новый компонент, который называется ChocolateCake. Существующий код остается нетронутым.

class SimpleCake implements Cake
{
    public function ingredients(): string
    {
        return 'Простой торт';
    }
    public function price(): double
    {
        return 12.5;
    }
}

WithWhippedCream — это класс декоратора, который расширяет CakeDecorator. Этот класс использует родительское свойство $cake, чтобы добавить поведение (декорировать) родителю.

class WithWhippedCream extends CakeDecorator
{
    public function ingredients(): string
    {
        return $this->cake->ingredients() + ' со взбитыми сливками';
    }
    public function price(): double
    {
        return $this->cake->price() + 2.5;
    }
}

Класс WithSprinkles выглядит почти так же, как класс WithWhippedCream. Разница между ними заключается в том, что каждый из них украшает своего родителя по-своему. Оба имеют разную цену и разные ингредиенты.

class WithSprinkles extends CakeDecorator
{
    public function ingredients(): string
    {
        return $this->cake->ingredients() + ' с обсыпкой';
    }
    public function price(): double
    {
        return $this->cake->price() + 1.25;
    }
}

Видите, как легко расширить функциональность? Допустим, пекарня хочет предоставить покупателям больше возможностей для украшения своих тортов. Например, им в голову пришла блестящую идея: начать продавать клубнику. Все, что нам нужно сделать, это создать новый декоратор под названием WithStrawberries. Опять же, существующий код остается нетронутым.

Настало время испечь наш собственный торт

Теперь, когда вся основная работа выполнена, пришло время готовки.

$cake = new SimpleCake();
$cake = new WithWhippedCream($cake);
$cake = new WithSprinkles($cake);
echo $cake->ingredients(); // Простой торт со взбитыми сливками с обсыпкой
echo $cake->price(); // 16.25
// или можно цепочкой
$cake = new WithSprinkles(new WithWhippedCream(new SimpleCake()));
echo $cake->ingredients(); // Простой торт со взбитыми сливками с обсыпкой
echo $cake->price(); // 16.25

Вот и всё! Мы успешно использовали паттерн Декоратора.

 

Автор: Daan
Перевод: Алексей Широков

Наши Телеграм и Вконтакте — следите за выходом новых статей через наши каналы.

Задать вопросы по урокам можно на нашем форуме.