Найти в Дзене

🌲 Паттерн Компоновщик: как работать с деревьями так же просто, как с листьями

Сегодня разберём паттерн 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 )

🌲 Паттерн Компоновщик: как работать с деревьями так же просто, как с листьями

Сегодня разберём паттерн 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 компоненты

<Button>Click me</Button>

<ButtonGroup>

<Button>Save</Button>

<Button>Cancel</Button>

</ButtonGroup>

Оба можно рендерить, стилизовать, скрывать одинаково.

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