Паттерн Декоратор (Decorator)
Декоратор (Decorator) — это структурный паттерн проектирования, который позволяет динамически добавлять объекту новые обязанности, оборачивая его в объекты-«декораторы». При этом исходный объект не изменяется, а дополнительная логика «подмешивается» извне.
Зачем нужен Декоратор:
Паттерн «Декоратор» позволяет гибко расширять функциональность объектов, не изменяя их код. Это особенно удобно, если нельзя (или нежелательно) использовать наследование.
Когда применять «Декоратор»?
1. Динамическое расширение
Когда нужно добавлять новые возможности объектам «на лету» без создания множества подклассов.
2. Ограничения наследования
Когда невозможно или нежелательно модифицировать класс напрямую (например, классы из сторонней библиотеки).
3. Множество пересекающихся особенностей
Когда несколько видов функционала надо сочетать по-разному. Декораторы позволяют комбинировать фичи, не создавая взрывного числа подклассов.
Принцип работы
- Определяется базовый интерфейс или абстрактный класс, который описывает основное поведение объекта.
- Конкретный класс реализует это поведение.
- Декоратор также реализует тот же базовый интерфейс, содержит ссылку на объект того же типа и делегирует ему работу, при этом добавляя своё поведение «до» или «после» основной логики.
Таким образом, декораторы могут оборачивать друг друга, создавая цепочку объектов, — каждый добавляет свои особенности.
Пример (TypeScript)
// 1. Базовый интерфейс для компонентов
interface DataSource {
writeData(data: string): void;
readData(): string;
}
// 2. Конкретный класс, реализующий интерфейс
class FileDataSource implements DataSource {
private filename: string;
private buffer: string;
constructor(filename: string) {
this.filename = filename;
this.buffer = "";
}
writeData(data: string): void {
// Логика записи данных в файл
this.buffer = data;
console.log(`Data "${data}" written to file: ${this.filename}`);
}
readData(): string {
// Логика чтения данных из файла
console.log(`Reading data from file: ${this.filename}`);
return this.buffer;
}
}
// 3. Общий класс-декоратор
class DataSourceDecorator implements DataSource {
protected wrappee: DataSource;
constructor(source: DataSource) {
this.wrappee = source;
}
writeData(data: string): void {
this.wrappee.writeData(data);
}
readData(): string {
return this.wrappee.readData();
}
}
// 4. Конкретные декораторы
class EncryptionDecorator extends DataSourceDecorator {
writeData(data: string): void {
const encrypted = btoa(data); // простенькая "шифрация" Base64
console.log("Encrypting data...");
super.writeData(encrypted);
}
readData(): string {
const data = super.readData();
console.log("Decrypting data...");
return atob(data);
}
}
class CompressionDecorator extends DataSourceDecorator {
writeData(data: string): void {
console.log("Compressing data...");
const compressed = `COMPRESSED(${data})`;
super.writeData(compressed);
}
readData(): string {
const data = super.readData();
console.log("Decompressing data...");
return data.replace(/^COMPRESSED\(|\)$/g, "");
}
}
// 5. Клиентский код
function clientCode() {
const file = new FileDataSource("data.txt");
// Оборачиваем файл декораторами
const encryption = new EncryptionDecorator(file);
const compression = new CompressionDecorator(encryption);
// Запись
compression.writeData("Hello, Decorator!");
// Чтение
console.log("Final data:", compression.readData());
}
clientCode();
- FileDataSource — базовая реализация, работающая с файлом.
- DataSourceDecorator — общий класс-декоратор, который принимает DataSource и реализует такой же интерфейс.
- EncryptionDecorator и CompressionDecorator добавляют свою логику, оборачивая вызовы writeData() и readData().
Преимущества
- Гибкость: можно на лету добавлять функциональность к объектам, создавая различные комбинации декораторов.
- Принцип единственной ответственности (SRP): каждое новое поведение оформляется как отдельный класс-декоратор.
- Повторное использование: один декоратор может применяться к разным объектам, не затрагивая их внутренний код.
Недостатки
- Усложнение структуры: При большом количестве декораторов цепочка может стать громоздкой для понимания.
- Сложность отладки: нужно следить за порядком оборачивания, ведь итоговое поведение зависит от последовательности декораторов.
Важно:
Если вы используете слишком много декораторов, это может усложнить логику следования операций. Оценивайте, насколько действительно нужна динамика и гибкость, прежде чем вводить паттерн «Декоратор».
Подробнее про Декоратор можно почитать здесь.