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

«Какое условие нужно соблюсти, чтобы паттерн Decorator оставался Decorator?» — вопрос из категории Паттерны, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

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

  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.