Какое условие нужно соблюсти, чтобы паттерн Decorator оставался Decorator?

Ответ

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

Основные принципы, которые нельзя нарушать:

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

Пример на PHP:

interface DataSource {
    public function writeData(string $data): void;
    public function readData(): string;
}

class FileDataSource implements DataSource {
    // ... реализация работы с файлом
}

class EncryptionDecorator implements DataSource {
    private DataSource $source;

    public function __construct(DataSource $source) {
        $this->source = $source; // Композиция
    }

    public function writeData(string $data): void {
        $encrypted = $this->encrypt($data);
        $this->source->writeData($encrypted); // Делегирование
    }

    public function readData(): string {
        $encrypted = $this->source->readData(); // Делегирование
        return $this->decrypt($encrypted);
    }
    // ... методы encrypt/decrypt
}
// Клиентский код использует DataSource, не зная, декоратор это или нет.
$source = new EncryptionDecorator(new FileDataSource('file.txt'));
$source->writeData('Sensitive data'); // Работает через один интерфейс

Если декоратор перестает делегировать вызовы или меняет интерфейс — это уже не паттерн Decorator.

Ответ 18+ 🔞

Слушай, а вот этот паттерн Decorator — он же, блядь, как хитрая жопа устроен. Вроде простая идея, а народ постоянно его ебёт, нарушая ключевые правила. Ёпта, сейчас разжую.

Главная фишка в том, что твой декоратор должен быть полной копией по интерфейсу того, что он оборачивает. Представь, у тебя есть простое окно. Ты на него вешаешь скроллбар — это декоратор. Клиент (пользователь) тыкает в окно, а ему похуй, есть там скролл или нет, он просто с окном работает. Окно и скролл-окно для него — одно и то же говно, с одним набором кнопок.

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

Второе — декоратор не должен заменять базовую логику, он должен её дополнять. Это как с тем кофе: ты в обычный эспрессо добавляешь молоко (декоратор «молочный»). Декоратор не варит новый кофе с нуля, он берет уже готовый эспрессо и хуяк — льёт в него молоко. Основа остаётся. В коде это выглядит как вызов метода у обёрнутого объекта ($this->source->writeData(...)). Нет этого вызова — значит, ты не дополнил, а подменил, и это уже не декоратор, а какая-то пиздопроебибна.

И третье, самое важное — композиция, а не наследование. Весь прикол в динамике. Ты не создаёшь класс EncryptedCompressedFileDataSource, который наследует всё подряд. Ты берёшь объект FileDataSource, заворачиваешь в EncryptionDecorator, а результат — заворачиваешь в CompressionDecorator. Всё, как матрёшка. Гибко, ёбана. Можно собрать любую комбинацию на ходу. Если ты это делаешь через гору наследования — ты мудак, у тебя будет овердохуища классов на каждый чих.

Смотри на примере, тут всё правильно:

interface DataSource {
    public function writeData(string $data): void;
    public function readData(): string;
}

class FileDataSource implements DataSource {
    // ... тут он просто пишет и читает в файл, как честный работяга
}

class EncryptionDecorator implements DataSource { // 1. Тот же интерфейс!
    private DataSource $source; // 3. Композиция, блядь!

    public function __construct(DataSource $source) {
        $this->source = $source;
    }

    public function writeData(string $data): void {
        $encrypted = $this->encrypt($data); // 2. Своя доп. логика ДО...
        $this->source->writeData($encrypted); // 2. ...делегирования основы
    }

    public function readData(): string {
        $encrypted = $this->source->readData(); // 2. Делегирование основы...
        return $this->decrypt($encrypted); // 2. ...и своя логика ПОСЛЕ
    }
    // ... методы encrypt/decrypt
}

Видишь? EncryptionDecorator — он такой же DataSource, как и файловая обёртка. Внутри у него сидит другой DataSource (композиция). Его методы вызывают тот же метод у обёрнутого объекта (делегирование), но обогащают эту хуйню шифрованием.

И клиенту да похуй:

// Клиенту всё равно, что внутри
$source = new EncryptionDecorator(new FileDataSource('file.txt'));
$source->writeData('Sensitive data'); // Просто работает

Если убрать делегирование ($this->source->writeData(...)) — базовая запись в файл не произойдёт. Если поменять интерфейс декоратора — клиент сломается. В обоих случаях паттерн накрылся медным тазом. Запомни: совместимость, делегирование, композиция. Всё остальное — от лукавого.