Коротка відповідь
middleDecorator — це паттерн, що дозволяє динамічно додавати нову поведінку до об’єкта, розширюючи його через вкладені декоратори. Кожен декоратор реалізує той самий інтерфейс, що й компонент і викликає його методи, додаючи власну логіку. Наприклад: `const logged = new LoggingDecorator(service);`
Повне пояснення
1. Що це і навіщо
Декоратор (Decorator) — структурний паттерн GoF, що дозволяє розширювати функціональність об’єкта без зміни його коду. Використовується, коли потрібна гнучка композиція поведінки (логування, кешування, аутентифікація тощо).
2. Принцип роботи / алгоритм
Паттерн складається з:
- Component – базовий інтерфейс/клас.
- ConcreteComponent – реальний об’єкт, що реалізує Component.
- Decorator – абстрактний клас/інтерфейс, що реалізує Component і містить посилання на компонент.
- ConcreteDecorator – конкретний декоратор, що додає нову поведінку.
Алгоритм: клієнт створює ConcreteComponent, потім обгортає його декораторами. Кожен декоратор викликає методи компоненту, додаючи свою логіку.
3. Реалізація (JS / TS)
- Базова реалізація
interface Coffee { getCost(): number; getDescription(): string; }
class SimpleCoffee implements Coffee {
getCost() { return 5; }
getDescription() { return 'Coffee'; }
}
abstract class CoffeeDecorator implements Coffee {
constructor(protected coffee: Coffee) {}
getCost() { return this.coffee.getCost(); }
getDescription() { return this.coffee.getDescription(); }
}
class MilkDecorator extends CoffeeDecorator {
getCost() { return super.getCost() + 1; }
getDescription() { return super.getDescription() + ', Milk'; }
}
- Веб‑dev приклад – додавання аутентифікації до HTTP‑запитів
interface HttpClient { get(url: string): Promise<any>; }
class AxiosClient implements HttpClient {
async get(url) { return axios.get(url); }
}
class AuthDecorator extends HttpClient {
constructor(private client: HttpClient, private token: string) {}
async get(url) {
const headers = { Authorization: `Bearer ${this.token}` };
return this.client.get(url, { headers });
}
}
- Новачковий приклад – просте логування
class LoggerDecorator extends CoffeeDecorator {
getCost() { console.log('Getting cost'); return super.getCost(); }
}
4. Тестування (Jest або Vitest)
test('MilkDecorator adds cost and description', () => {
const coffee = new SimpleCoffee();
const milkCoffee = new MilkDecorator(coffee);
expect(milkCoffee.getCost()).toBe(6);
expect(milkCoffee.getDescription()).toContain('Milk');
});
5. Проблеми та edge cases
- Неправильне використання ролей – декоратор не реалізує Component, а просто розширює клас.
- Зайва абстракція – надмірна кількість декораторів ускладнює трасування.
- Cyclical dependencies – декоратор, що посилається назад на той самий тип.
- Плутанина між Decorator і Proxy – обидва розширюють інтерфейс, але Proxy контролює доступ.
- Performance‑пастки – глибока вкладка декораторів збільшує стек викликів і накладні витрати.
6. Оптимізація та продуктивність
- Кожен декоратор додає один рівень обгортки; у випадку 10‑х декораторів – 10 додаткових викликів.
- Пам’ять: кожен об’єкт зберігає посилання на компонент, що збільшує використання heap‑у.
- Для критичних шляхів можна об’єднати декоратори в один, або використовувати композицію замість вкладки.
7. Без JS / архітектурні альтернативи
- У CSS можна досягти подібної «декорування» через класові модифікатори (BEM) або CSS‑препроцесори.
- У HTML/ARIA можна додавати атрибути
data-*для розширення поведінки без JavaScript. - У чистій архітектурі можна використовувати middleware‑шаблон (наприклад, у NestJS) замість декораторів.
8. Cheatsheet + follow-up
- Cheatsheet:
Component → ConcreteComponent → Decorator (abstract) → ConcreteDecorator. - Interview Q1: Як декоратор відрізняється від проксі?
- Interview Q2: Які переваги і недоліки має глибока вкладка декораторів?