🌲 Паттерн Компоновщик: как работать с деревьями так же просто, как с листьями Сегодня разберём паттерн 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