Компоновщик на TypeScript
Компоновщик — это структурный паттерн, который позволяет создавать дерево объектов и работать с ним так же, как и с единичным объектом.
Компоновщик давно стал синонимом всех задач, связанных с построением дерева объектов. Все операции компоновщика основаны на рекурсии и «суммировании» результатов на ветвях дерева.
Сложность:
Популярность:
Применимость: Паттерн Компоновщик встречается в любых задачах, которые связаны с построением дерева. Самый простой пример — составные элементы GUI, которые тоже можно рассматривать как дерево.
Признаки применения паттерна: Если из объектов строится древовидная структура, и со всеми объектами дерева, как и с самим деревом работают через общий интерфейс.
Концептуальный пример
Этот пример показывает структуру паттерна Компоновщик , а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
index.ts: Пример структуры паттерна
/**
* Базовый класс Компонент объявляет общие операции как для простых, так и для
* сложных объектов структуры.
*/
abstract class Component {
protected parent!: Component | null;
/**
* При необходимости базовый Компонент может объявить интерфейс для
* установки и получения родителя компонента в древовидной структуре. Он
* также может предоставить некоторую реализацию по умолчанию для этих
* методов.
*/
public setParent(parent: Component | null) {
this.parent = parent;
}
public getParent(): Component | null {
return this.parent;
}
/**
* В некоторых случаях целесообразно определить операции управления
* потомками прямо в базовом классе Компонент. Таким образом, вам не нужно
* будет предоставлять конкретные классы компонентов клиентскому коду, даже
* во время сборки дерева объектов. Недостаток такого подхода в том, что эти
* методы будут пустыми для компонентов уровня листа.
*/
public add(component: Component): void { }
public remove(component: Component): void { }
/**
* Вы можете предоставить метод, который позволит клиентскому коду понять,
* может ли компонент иметь вложенные объекты.
*/
public isComposite(): boolean {
return false;
}
/**
* Базовый Компонент может сам реализовать некоторое поведение по умолчанию
* или поручить это конкретным классам, объявив метод, содержащий поведение
* абстрактным.
*/
public abstract operation(): string;
}
/**
* Класс Лист представляет собой конечные объекты структуры. Лист не может иметь
* вложенных компонентов.
*
* Обычно объекты Листьев выполняют фактическую работу, тогда как объекты
* Контейнера лишь делегируют работу своим подкомпонентам.
*/
class Leaf extends Component {
public operation(): string {
return 'Leaf';
}
}
/**
* Класс Контейнер содержит сложные компоненты, которые могут иметь вложенные
* компоненты. Обычно объекты Контейнеры делегируют фактическую работу своим
* детям, а затем «суммируют» результат.
*/
class Composite extends Component {
protected children: Component[] = [];
/**
* Объект контейнера может как добавлять компоненты в свой список вложенных
* компонентов, так и удалять их, как простые, так и сложные.
*/
public add(component: Component): void {
this.children.push(component);
component.setParent(this);
}
public remove(component: Component): void {
const componentIndex = this.children.indexOf(component);
this.children.splice(componentIndex, 1);
component.setParent(null);
}
public isComposite(): boolean {
return true;
}
/**
* Контейнер выполняет свою основную логику особым образом. Он проходит
* рекурсивно через всех своих детей, собирая и суммируя их результаты.
* Поскольку потомки контейнера передают эти вызовы своим потомкам и так
* далее, в результате обходится всё дерево объектов.
*/
public operation(): string {
const results = [];
for (const child of this.children) {
results.push(child.operation());
}
return `Branch(${results.join('+')})`;
}
}
/**
* Клиентский код работает со всеми компонентами через базовый интерфейс.
*/
function clientCode(component: Component) {
// ...
console.log(`RESULT: ${component.operation()}`);
// ...
}
/**
* Таким образом, клиентский код может поддерживать простые компоненты-листья...
*/
const simple = new Leaf();
console.log('Client: I\'ve got a simple component:');
clientCode(simple);
console.log('');
/**
* ...а также сложные контейнеры.
*/
const tree = new Composite();
const branch1 = new Composite();
branch1.add(new Leaf());
branch1.add(new Leaf());
const branch2 = new Composite();
branch2.add(new Leaf());
tree.add(branch1);
tree.add(branch2);
console.log('Client: Now I\'ve got a composite tree:');
clientCode(tree);
console.log('');
/**
* Благодаря тому, что операции управления потомками объявлены в базовом классе
* Компонента, клиентский код может работать как с простыми, так и со сложными
* компонентами, вне зависимости от их конкретных классов.
*/
function clientCode2(component1: Component, component2: Component) {
// ...
if (component1.isComposite()) {
component1.add(component2);
}
console.log(`RESULT: ${component1.operation()}`);
// ...
}
console.log('Client: I don\'t need to check the components classes even when managing the tree:');
clientCode2(tree, simple);
Output.txt: Результат выполнения
Client: I've got a simple component:
RESULT: Leaf
Client: Now I've got a composite tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf))
Client: I don't need to check the components classes even when managing the tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf)