Декоратор

Аналогія

Допустимо, у вас свій автосервіс, що надає різні послуги. Як виставляти клієнтам рахунок? Додавати послідовно послуги та їх вартість - і врешті-решт вийде підсумкова сума до оплати. Тут кожен тип послуги це «декоратор».

Стисло

Шаблон «Декоратор» дозволяє динамічно змінювати поведінку об'єкта під час виконання, обгортаючи його в об'єкт класу «декоратора».

Вікіпедія

Шаблон «Декоратор» дозволяє підключати до об'єкта додаткову поведінку (статично чи динамічно), не впливаючи на поведінку інших об'єктів того ж класу. Шаблон часто використовується для дотримання принципу єдиного обов'язку (Single Responsibility Principle), оскільки дозволяє розділити функціональність між класами для вирішення конкретних завдань.

Приклад

Візьмемо як приклад кави. Спочатку просто реалізуємо інтерфейс:

interface Coffee
{
    public function getCost();
    public function getDescription();
}

class SimpleCoffee implements Coffee
{
    public function getCost()
    {
        return 10;
    }

    public function getDescription()
    {
        return 'Simple coffee';
    }
}

Можна зробити код, що розширюється, щоб при необхідності вносити модифікації. Додамо «декоратори»:

class MilkCoffee implements Coffee
{
    protected $coffee;

    public function __construct(Coffee $coffee)
    {
        $this->coffee = $coffee;
    }

    public function getCost()
    {
        return $this->coffee->getCost() + 2;
    }

    public function getDescription()
    {
        return $this->coffee->getDescription() . ', milk';
    }
}

class WhipCoffee implements Coffee
{
    protected $coffee;

    public function __construct(Coffee $coffee)
    {
        $this->coffee = $coffee;
    }

    public function getCost()
    {
        return $this->coffee->getCost() + 5;
    }

    public function getDescription()
    {
        return $this->coffee->getDescription() . ', whip';
    }
}

class VanillaCoffee implements Coffee
{
    protected $coffee;

    public function __construct(Coffee $coffee)
    {
        $this->coffee = $coffee;
    }

    public function getCost()
    {
        return $this->coffee->getCost() + 3;
    }

    public function getDescription()
    {
        return $this->coffee->getDescription() . ', vanilla';
    }
}

Тепер приготуємо каву:

$someCoffee = новий SimpleCoffee();
echo $someCoffee->getCost(); // 10
echo $someCoffee->getDescription(); // Simple Coffee

$ someCoffee = New MilkCoffee ($ someCoffee);
echo $someCoffee->getCost(); // 12
echo $someCoffee->getDescription(); // Simple Coffee, milk

$ someCoffee = новий WhipCoffee ($ someCoffee);
echo $someCoffee->getCost(); // 17
echo $someCoffee->getDescription(); // Simple Coffee, milk, whip

$ someCoffee = новий VanillaCoffee ($ someCoffee);
echo $someCoffee->getCost(); // 20
echo $someCoffee->getDescription(); // Simple Coffee, milk, whip, vanilla