Composite Pattern — Композит
Единый интерфейс к дереву. Лист, узел, всё дерево — обрабатываются одинаково.
Проблема
Древовидная структура: смета (товар → подгруппа → группа), файловая система (файл → папка), DOM (узел → элемент → дерево). Хочется работать с любым узлом одинаково: получить «общую сумму», «полное имя», «количество». Composite избавляет от условий типа if (item.isGroup) по всему коду.
Где используется
- DOM-дерево: элемент и документ обрабатываются аналогично
- Файловая система: файл и папка с одинаковым API для
size,print - UI-компоненты: кнопка и группа кнопок
- Корзина интернет-магазина: товар и бандл с
getPrice - Разрешения: одиночное право и группа прав
- AST в парсерах
Решение
- Один абстрактный интерфейс на всё дерево
- Лист реализует операцию как базовый случай
- Узел реализует операцию через рекурсивный обход детей
Реализации
Файловая система
class FileSystemItem {
constructor(name) { this.name = name; }
getSize { throw new Error('abstract'); }
print(indent = '') { throw new Error('abstract'); }
}
class File extends FileSystemItem {
constructor(name, size) { super(name); this.size = size; }
getSize { return this.size; }
print(indent = '') { console.log(`${indent}📄 ${this.name} (${this.size} KB)`); }
}
class Directory extends FileSystemItem {
constructor(name) { super(name); this.children = ; }
add(item) { this.children.push(item); return this; }
remove(item) { this.children = this.children.filter(c => c !== item); return this; }
getSize { return this.children.reduce((sum, c) => sum + c.getSize, 0); }
print(indent = '') {
console.log(`${indent}📁 ${this.name}/`);
this.children.forEach(c => c.print(indent + ' '));
}
}
const root = new Directory('root');
const src = new Directory('src');
src.add(new File('index.js', 12)).add(new File('utils.js', 8));
root.add(src).add(new File('README.md', 2));
root.print;
console.log('Размер:', root.getSize, 'KB');
Корзина с бандлами
class Product {
constructor(name, price) { this.name = name; this.price = price; }
getPrice { return this.price; }
}
class Bundle {
constructor(name, discount = 0) {
this.name = name;
this.items = ;
this.discount = discount;
}
add(item) { this.items.push(item); return this; }
getPrice {
const total = this.items.reduce((s, i) => s + i.getPrice, 0);
return total * (1 - this.discount);
}
}
const cart = new Bundle('Корзина');
cart.add(new Product('Ноутбук', 80000));
const workBundle = new Bundle('Рабочий', 0.1);
workBundle.add(new Product('Мышь', 3000)).add(new Product('Клавиатура', 5000));
cart.add(workBundle);
cart.getPrice; // 80000 + (8000 * 0.9) = 87200
Простой Composite (одинаковый интерфейс cost)
class CartItem {
constructor(name, price, qty = 1) { this.name = name; this.price = price; this.qty = qty; }
cost { return this.price * this.qty; }
}
class CartGroup {
constructor(name, items = ) { this.name = name; this.items = items; }
cost { return this.items.reduce((sum, i) => sum + i.cost, 0); }
}
const order = new CartGroup('order', [
new CartItem('apple', 10, 5),
new CartGroup('drinks', [
new CartItem('water', 20, 2),
new CartItem('cola', 30, 1),
]),
]);
order.cost; // 50 + 40 + 30 = 120
Где используется в JS-экосистеме
- DOM:
node.querySelector,node.children,node.appendChild— один API на любом узле - React/Vue компоненты — composite-дерево
- JSON-структуры — естественный composite
- Filesystem API —
fs.stat,readdirRecursive - AST в парсерах — composite классическое применение
Подводные камни
- Composite vs Facade: Composite — дерево похожих узлов одного интерфейса; корень дерева можно назвать facade'ом, но это специализированный случай.
- Composite vs Decorator: Decorator оборачивает один узел; Composite собирает много в дерево.
- Если в дереве узлы слишком разные по поведению — единый интерфейс ломается.
- Производительность: рекурсивный обход больших деревьев может быть дорогим — нужен кэш или итеративный обход (stack overflow).
- Нарушение LSP: Leaf реализует методы
add/removeс заглушками. Лучше выноситьadd/removeтолько в Container.
Главные тезисы автора
- «Общий интерфейс — и доступаемся к листочку, к ноде или ко всему дереву».
- «Корень дерева можно назвать фасадом» — Composite это специализированный Facade.
- Пример автора: смета с подгруппами — каждый узел знает свою стоимость и сумму подузлов.
- DOM — канонический пример Composite, который JS-разработчик использует ежедневно.
🎓 Источники
- 🎓 GoF Patterns Обзор всех паттернов · 2025-04-29
- DOM как пример Composite
- Смета с товарами и подгруппами
- Различие Composite и Facade
- 🎓 Адаптер, Декоратор, Прокси, Фасад — В чём разница · 2026-02-09
- Composite — дерево похожих сущностей одного интерфейса
- refactoring.guru — Composite