Ответ
Ключевое условие — сохранение идентичности интерфейса. Декоратор должен реализовывать тот же интерфейс (или расширять тот же абстрактный класс), что и декорируемый компонент, и делегировать ему базовую операцию.
Основные принципы, которые нельзя нарушать:
- Совместимость по интерфейсу: Клиентский код должен работать с декоратором так же, как и с исходным объектом, не зная об обертке.
- Делегирование: Декоратор не заменяет, а дополняет поведение компонента, вызывая его метод и добавляя свою логику до или после.
- Композиция над наследованием: Функциональность добавляется динамически через композицию объектов, а не статически через создание подклассов.
Пример на 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(...)) — базовая запись в файл не произойдёт. Если поменять интерфейс декоратора — клиент сломается. В обоих случаях паттерн накрылся медным тазом. Запомни: совместимость, делегирование, композиция. Всё остальное — от лукавого.