Skip to main content
Практика завдань

Патерн декоратор

Декоратор — це структурний патерн проектування, який дозволяє динамічно додавати нові обов'язки до об'єкта, обгортаючи його в "декораторні" об'єкти. При цьому оригінальний об'єкт не змінюється, а додаткова логіка "змішується" ззовні.

Чому потрібен Декоратор:

Патерн декоратора дозволяє гнучко розширювати функціональність об'єктів без зміни їх коду. Це особливо зручно, якщо наслідування не може (або не повинно) використовуватися.

Коли застосовувати "Декоратор"?

1. Динамічне розширення

Коли потрібно додати нові можливості до об'єктів "на льоту", не створюючи багато підкласів.

2. Обмеження наслідування

Коли неможливо або небажано безпосередньо змінювати клас (наприклад, класи з бібліотеки третьої сторони)

3. Кілька перетинаючих функцій

Коли кілька типів функціональності потрібно комбінувати по-різному. Декоратори дозволяють комбінувати функції без створення вибухової кількості підкласів.


Як це працює

  1. Визначається базовий інтерфейс або абстрактний клас, який описує основну поведінку об'єкта.
  2. Конкретний клас реалізує цю поведінку.
  3. Декоратор також реалізує той самий базовий інтерфейс, містить посилання на об'єкт того ж типу і делегує роботу йому, додаючи власну поведінку "до" або "після" основної логіки.

Таким чином, декоратори можуть обгортати один одного, створюючи ланцюг об'єктів — кожен додає свої функції.


Приклад (TypeScript)

ts
// 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}" записані у файл: ${this.filename}`); } readData(): string { // Логіка читання даних з файлу console.log(`Читання даних з файлу: ${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("Шифрування даних..."); super.writeData(encrypted); } readData(): string { const data = super.readData(); console.log("Розшифрування даних..."); return atob(data); } } class CompressionDecorator extends DataSourceDecorator { writeData(data: string): void { console.log("Стиснення даних..."); const compressed = `COMPRESSED(${data})`; super.writeData(compressed); } readData(): string { const data = super.readData(); console.log("Розпакування даних..."); 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("Привіт, Декоратор!"); // Читання console.log("Фінальні дані:", compression.readData()); } clientCode();
  • FileDataSource — базова реалізація, що працює з файлом.
  • DataSourceDecorator — загальний клас декоратора, який приймає DataSource і реалізує той самий інтерфейс.
  • EncryptionDecorator та CompressionDecorator додають свою логіку, обгортаючи виклики writeData() та readData().

Переваги

  • Гнучкість: можна додавати функціональність до об'єктів на льоту, створюючи різні комбінації декораторів.
  • Принцип єдиної відповідальності (SRP): кожна нова поведінка формалізується як окремий клас декоратора.
  • Повторне використання: один декоратор може бути застосований до різних об'єктів без впливу на їх внутрішній код.

Недоліки

  • Складність структури: з великою кількістю декораторів ланцюг може стати громіздким для розуміння.
  • Складність налагодження: потрібно стежити за порядком обгортання, оскільки фінальна поведінка залежить від послідовності декораторів.

Важливо: Якщо ви використовуєте занадто багато декораторів, це може ускладнити логіку послідовності операцій. Оцініть, скільки динаміки та гнучкості дійсно потрібно, перш ніж впроваджувати патерн Декоратор.

Читати більше про Декоратор тут.

Зміст

Коли застосовувати "Декоратор"?Як це працюєПриклад (TypeScript)ПеревагиНедоліки

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?
Практика завдань