🌲 Паттерн Компоновщик: как работать с деревьями так же просто, как с листьями Сегодня разберём паттерн Composite (Компоновщик) — элегантный способ работы с иерархическими структурами.
Суть: работаешь с группой объектов точно так же, как с одним объектом.
Проблема: дерево объектов, куча if-ов
Делаешь файловый менеджер. Нужно посчитать размер выбранного элемента.
Первое решение:
function calculateSize(item: File | Folder): number { if (item.type === 'file') { return item.size; } else { let totalSize = 0; for (const child of item.children) { if (child.type === 'file') { totalSize += child.size; } else { totalSize += calculateSize(child); // рекурсия } } return totalSize; } }
Проблемы:
- Постоянные проверки типа
- Разная логика для файлов и папок
- Добавишь новый тип — переписывай всё
А ведь концептуально файл и папка делают одно — отдают размер. Просто папка суммирует детей.
Решение: единый интерфейс interface FileSystemItem { name: string; getSize(): number; }
class File implements FileSystemItem { constructor( public name: string, private size: number ) {}
getSize(): number { return this.size; } }
class Folder implements FileSystemItem { private children: FileSystemItem[] = [];
constructor(public name: string) {}
add(item: FileSystemItem): void { this.children.push(item); }
getSize(): number { // Магия! Неважно, файл это или папка return this.children.reduce( (total, child) => total + child.getSize(), 0 ); } }
Использование:
const root = new Folder('project'); const src = new Folder('src');
src.add(new File('index.ts', 1500)); src.add(new File('app.ts', 3200));
root.add(src); root.add(new File('README.md', 800));
// Работаем одинаково! console.log(src.getSize()); // 4700 console.log(root.getSize()); // 5500
Никаких if-ов. Вызываешь getSize() — неважно, файл это или папка с тысячей вложенных элементов. Реальные примеры 1. DOM дерево const button = document.querySelector('button'); const div = document.querySelector('div');
button.remove(); // Удалить элемент div.remove(); // Удалить контейнер со всеми детьми
И элемент, и контейнер имеют одинаковые методы.
2. UI компоненты Click me
Save Cancel
Оба можно рендерить, стилизовать, скрывать одинаково.
3. Валидация форм interface Validator { validate(value: any): boolean; }
class EmailValidator implements Validator { validate(value: string): boolean { return /^[^s@]+@[^s@]+.[^s@]+$/.test(value); } }
class ValidatorGroup implements Validator { private validators: Validator[] = [];
add(validator: Validator): void { this.validators.push(validator); }
validate(value: any): boolean { return this.validators.every(v => v.validate(value)); } }
Когда применять?
✅ Используй:
- Древовидная структура объектов
- Нужно работать с элементами и группами одинаково
- Хочешь избавиться от if (type === 'file')
❌ Не используй:
- Структура плоская (просто массив)
- Логика для элементов и контейнеров сильно отличается
Главная мысль
Компоновщик — про единообразие. Работаешь с элементом и группой через один интерфейс.
Это даёт:
- Простоту — не нужны проверки типа
- Гибкость — легко добавлять элементы
- Элегантность — код чище
Когда видишь if (isFolder) и if (isFile) — время для Компоновщика.
Используешь этот паттерн? 💬
#архитектура #паттерныпроектирования #frontend #typescript #composite
· 25.12.2025
Эх, к сожалению markdown разметка для кода в Сетке на работает
Юрий Долганов, есть в планах такое?
0
ответить
коммент скрыт — часть юзеров считает его токсичным или некорректным
коммент удалён
· 25.12.2025
Есть такое, попробуй подправить, просто стоит постинг из тг
0
ответить
коммент скрыт — часть юзеров считает его токсичным или некорректным
ответ удалён