Что такое паттерн Decorator?

Ответ

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

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

Пример на PHP (кофе с добавками):

// Базовый интерфейс
interface Coffee {
    public function getCost(): float;
    public function getDescription(): string;
}

// Конкретный компонент
class SimpleCoffee implements Coffee {
    public function getCost(): float {
        return 50.0;
    }
    public function getDescription(): string {
        return "Simple coffee";
    }
}

// Базовый декоратор
abstract class CoffeeDecorator implements Coffee {
    protected Coffee $coffee;

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

    public function getCost(): float {
        return $this->coffee->getCost();
    }

    public function getDescription(): string {
        return $this->coffee->getDescription();
    }
}

// Конкретные декораторы
class MilkDecorator extends CoffeeDecorator {
    public function getCost(): float {
        return parent::getCost() + 20.0;
    }
    public function getDescription(): string {
        return parent::getDescription() . ", milk";
    }
}

class WhipDecorator extends CoffeeDecorator {
    public function getCost(): float {
        return parent::getCost() + 30.0;
    }
    public function getDescription(): string {
        return parent::getDescription() . ", whip";
    }
}

// Использование
$coffee = new SimpleCoffee();
echo $coffee->getDescription() . " costs " . $coffee->getCost() . "n";
// Simple coffee costs 50

$coffee = new MilkDecorator($coffee);
echo $coffee->getDescription() . " costs " . $coffee->getCost() . "n";
// Simple coffee, milk costs 70

$coffee = new WhipDecorator($coffee);
echo $coffee->getDescription() . " costs " . $coffee->getCost() . "n";
// Simple coffee, milk, whip costs 100

Преимущества:

  • Гибкость: Поведение можно добавлять и комбинировать во время выполнения.
  • Соответствует принципу открытости/закрытости: Классы компонентов закрыты для модификации, но открыты для расширения.
  • Избегает сложных иерархий подклассов.

Недостатки:

  • Может создать много маленьких классов.
  • Цепочки декораторов иногда сложно конфигурировать и отлаживать.

Ответ 18+ 🔞

А, паттерн Декоратор, ёпта! Ну это ж классика, как бутерброд с колбасой, на который сверху можно навернуть сыр, икру, или, если совсем охуевший, ещё и майонезом пиздануть. Суть проще пареной репы, но мозг иногда сносит, когда начинаешь в цепочках этих обёрток разбираться.

Смотри, представь себе — есть у тебя объект, простой как три копейки. Кофе, например. И ты такой: «Хочу с молоком». Наследование — это как родить нового ребёнка специально для кофе с молоком. А если ещё и с сахаром? Рожать ещё одного. С корицей? Да ты ёбнутый, вся родословная будет из одних кофеёв. Вот тут-то Декоратор и выручает, ядрёна вошь!

Фишка в чём: ты берёшь свой объект-кофе и заворачиваешь его в другую обёртку (декоратор), которая делает вид, что она тоже кофе. А внутри себя эта обёртка просто тыкает в оригинальный объект и говорит: «Эй, чувак, дай свою цену и описание, а я сверху своё накину». И так можно оборачивать в овердохуищу слоёв, как луковицу. Получается гибко — добавил обёртку, получил новую фичу, не трогая старый код. Принцип «открыт для расширения, закрыт для изменений» в действии, ебать мои старые костыли!

Вот смотри на примере, тут всё понятно станет. Смотри код, он рабочий, я не трогал его, как велено.

// Базовый интерфейс — что умеет любой кофе?
interface Coffee {
    public function getCost(): float;
    public function getDescription(): string;
}

// Конкретный компонент — просто кофе, голый, без всего
class SimpleCoffee implements Coffee {
    public function getCost(): float {
        return 50.0;
    }
    public function getDescription(): string {
        return "Simple coffee";
    }
}

// А вот и главный трюк — базовый декоратор.
// Он тоже кофе, но внутри у него спрятан ДРУГОЙ кофе.
abstract class CoffeeDecorator implements Coffee {
    protected Coffee $coffee; // Вот эта ссылка — вся магия

    public function __construct(Coffee $coffee) {
        $this->coffee = $coffee; // Запоминаем, что оборачиваем
    }

    // По умолчанию просто прокидывает вызовы внутрь
    public function getCost(): float {
        return $this->coffee->getCost();
    }

    public function getDescription(): string {
        return $this->coffee->getDescription();
    }
}

// Конкретные декораторы — они уже добавляют свою цену и описание
class MilkDecorator extends CoffeeDecorator {
    public function getCost(): float {
        // Берём стоимость того, что внутри, и добавляем своё
        return parent::getCost() + 20.0;
    }
    public function getDescription(): string {
        return parent::getDescription() . ", milk";
    }
}

class WhipDecorator extends CoffeeDecorator {
    public function getCost(): float {
        return parent::getCost() + 30.0;
    }
    public function getDescription(): string {
        return parent::getDescription() . ", whip";
    }
}

// Использование — вот где веселье начинается!
$coffee = new SimpleCoffee();
echo $coffee->getDescription() . " costs " . $coffee->getCost() . "n";
// Выведет: Simple coffee costs 50

// Оборачиваем в молоко
$coffee = new MilkDecorator($coffee);
echo $coffee->getDescription() . " costs " . $coffee->getCost() . "n";
// Выведет: Simple coffee, milk costs 70

// Оборачиваем в сливки (взбитые, whip)
$coffee = new WhipDecorator($coffee);
echo $coffee->getDescription() . " costs " . $coffee->getCost() . "n";
// Выведет: Simple coffee, milk, whip costs 100

Плюсы, блядь, очевидные:

  • Гибкость пиздец. Захотел с сиропом — создал SyrupDecorator и обернул. Не понравилось — убрал. Всё в рантайме, без перекомпиляций и новых родов.
  • Не нужно плодить сущности. Не будет у тебя класса CoffeeWithMilkAndSugarAndCinnamonAndWhip. Вместо этого — цепочка обёрток. Красота.
  • Принцип открытости/закрытости в деле. Старый код SimpleCoffee не трогаешь, а новую функциональность добавляешь новыми классами. Чисто, опрятно.

Но и минусы есть, куда ж без них:

  • Много мелких классов. Создашь CaramelDecorator, IceDecorator, VanillaDecorator — и в проекте уже доверия ебать ноль, кто за что отвечает. Можно запутаться, как в трёх соснах.
  • Конфигурирование цепочек. Иногда, чтобы собрать нужный объект, нужно написать строчку кода длиннее, чем твоё терпение. Особенно если декораторов много и они зависят друг от друга. Терпения ноль ебать к концу дня.
  • Отладка. Попробуй отладить цепочку из 5 обёрток, где каждая что-то меняет. Это тот ещё квест, чувак. Волнение ебать гарантировано.

Короче, инструмент мощный, но как молоток — можно гвоздь забить, а можно по пальцам получить. Используй с умом, а не просто потому, что модно.