Коротка відповідь
middleComposite — це структура, що дозволяє об’єднувати одиниці та групи в дерево, де клієнт працює з об’єктами однаково. У TS це реалізується через інтерфейс `Component` з методом `operation()`, а класами `Leaf` і `Composite` реалізують його, причому `Composite` зберігає масив дочірніх `Component`. Приклад: `const root = new Composite(); root.add(new Leaf()); root.operation();`
Повне пояснення
1. Що це і навіщо
Composite (Компоновщик) — композиційний паттерн, що дозволяє клієнту працювати з одиночними об’єктами і групами їх однаково. Підходить для деревоподібних структур, наприклад UI‑компоненти, файлові системи, організаційні схеми.
2. Принцип роботи / алгоритм
- Component: абстрактний базовий клас/інтерфейс, що оголошує спільні операції.
- Leaf: кінцева одиниця, не має дочірніх елементів.
- Composite: містить колекцію
Component, реалізує ті ж операції, викликаючи їх у дочірніх. - Діаграма: клієнт викликає
operation()на корені, який рекурсивно розповсюджує виклик по всій структурі.
3. Реалізація (JS / TS)
- Базова реалізація
interface Component { operation(): void; }
class Leaf implements Component {
constructor(private name: string) {}
operation() { console.log(`Leaf ${this.name}`); }
}
class Composite implements Component {
private children: Component[] = [];
add(c: Component) { this.children.push(c); }
operation() {
console.log('Composite start');
this.children.forEach(c => c.operation());
console.log('Composite end');
}
}
- Реалістичний приклад (UI‑дерево)
class UIComponent implements Component {
constructor(public id: string) {}
render() { console.log(`Render ${this.id}`); }
}
class Container extends UIComponent {
private children: UIComponent[] = [];
add(c: UIComponent) { this.children.push(c); }
render() {
console.log(`Container ${this.id} start`);
this.children.forEach(c => c.render());
console.log(`Container ${this.id} end`);
}
}
- Спрощений для новачка
class Node {
constructor(public name: string, public children: Node[] = []) {}
print() { console.log(this.name); this.children.forEach(c => c.print()); }
}
4. Тестування (Jest або Vitest)
test('Composite renders all leaves', () => {
const leaf1 = new Leaf('A');
const leaf2 = new Leaf('B');
const comp = new Composite();
comp.add(leaf1); comp.add(leaf2);
const spy = jest.spyOn(console, 'log');
comp.operation();
expect(spy).toHaveBeenCalledWith('Leaf A');
expect(spy).toHaveBeenCalledWith('Leaf B');
});
5. Проблеми та edge cases
- Неправильне використання ролей – додавання
CompositeуLeafабо навпаки. - Зайва абстракція – коли дерево одношарове, паттерн лише збільшує складність.
- Cyclical dependencies – рекурсивне додавання себе в
Compositeпризводить до нескінченної циклічної структури. - Плутанина з Visitor – Composite часто комбінують із Visitor, але не треба змішувати їх логіку.
- Performance‑пастки – глибоке дерево викликає багато рекурсивних викликів, що збільшує стек‑пам’ять.
6. Оптимізація та продуктивність
- Рекурсивні виклики можуть бути замінені на ітераційний стек, щоб уникнути переповнення.
- Кешування результатів
operation()у вузлах, якщо вони не змінюються. - Використання lazy‑loading для дочірніх елементів, щоб не створювати всі об’єкти одразу.
7. Без JS / архітектурні альтернативи
- У HTML можна реалізувати дерево через
<ul>/<li>або<div>зdata-атрибутами, а логіку обробки – через рекурсивний рендер у шаблоні. - У CSS можна використовувати
display: contentsдля імітації композиції без додаткових об’єктів. - У чистій архітектурі можна просто мати масив об’єктів з полем
parentId, а клієнт працює через рекурсивний пошук.
8. Cheatsheet + follow-up
- Composite:
Component→Leaf | Composite. Рекурсивна операція. - Питання: 1) Як уникнути циклічних залежностей у Composite? 2) Коли варто обирати Composite замість простого масиву?