Паттерн Компоновщик на C++
Компоновщик – это структурный паттерн, который позволяет создавать дерево объектов и работать с ним так же, как и с единичным объектом.
Компоновщик давно стал синонимом всех задач, связанных с построением дерева объектов. Все операции компоновщика основаны на рекурсии и «суммировании» результатов на ветвях дерева.
Особенности паттерна на C++
Сложность:
Популярность:
Применимость: Паттерн Компоновщик встречается в любых задачах, которые связаны с построением дерева. Самый простой пример – составные элементы GUI, которые тоже можно рассматривать как дерево.
Признаки применения паттерна: Если из объектов строится древовидная структура, и со всеми объектами дерева, как и с самим деревом работают через общий интерфейс.
Концептуальный пример
Этот пример показывает структуру паттерна Компоновщик, а именно – из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
main.cc: Пример структуры паттерна
#include <algorithm>
#include <iostream>
#include <list>
#include <string>
/**
* Базовый класс Компонент объявляет общие операции как для простых, так и для
* сложных объектов структуры.
*/
class Component {
/**
* @var Component
*/
protected:
Component *parent_;
/**
* При необходимости базовый Компонент может объявить интерфейс для установки
* и получения родителя компонента в древовидной структуре. Он также может
* предоставить некоторую реализацию по умолчанию для этих методов.
*/
public:
virtual ~Component() {}
void SetParent(Component *parent) {
this->parent_ = parent;
}
Component *GetParent() const {
return this->parent_;
}
/**
* В некоторых случаях целесообразно определить операции управления потомками
* прямо в базовом классе Компонент. Таким образом, вам не нужно будет
* предоставлять конкретные классы компонентов клиентскому коду, даже во время
* сборки дерева объектов. Недостаток такого подхода в том, что эти методы
* будут пустыми для компонентов уровня листа.
*/
virtual void Add(Component *component) {}
virtual void Remove(Component *component) {}
/**
* Вы можете предоставить метод, который позволит клиентскому коду понять,
* может ли компонент иметь вложенные объекты.
*/
virtual bool IsComposite() const {
return false;
}
/**
* Базовый Компонент может сам реализовать некоторое поведение по умолчанию
* или поручить это конкретным классам, объявив метод, содержащий поведение
* абстрактным.
*/
virtual std::string Operation() const = 0;
};
/**
* Класс Лист представляет собой конечные объекты структуры. Лист не может иметь
* вложенных компонентов.
*
* Обычно объекты Листьев выполняют фактическую работу, тогда как объекты
* Контейнера лишь делегируют работу своим подкомпонентам.
*/
class Leaf : public Component {
public:
std::string Operation() const override {
return "Leaf";
}
};
/**
* Класс Контейнер содержит сложные компоненты, которые могут иметь вложенные
* компоненты. Обычно объекты Контейнеры делегируют фактическую работу своим
* детям, а затем «суммируют» результат.
*/
class Composite : public Component {
protected:
std::list<Component *> children_;
public:
/**
* Объект контейнера может как добавлять компоненты в свой список вложенных
* компонентов, так и удалять их, как простые, так и сложные.
*/
void Add(Component *component) override {
this->children_.push_back(component);
component->SetParent(this);
}
/**
* Имейте в виду, что этот метод удаляет указатель на список,
* но не освобождает память, вы должны делать это вручную или лучше
* использовать умные указатели.
*/
void Remove(Component *component) override {
children_.remove(component);
component->SetParent(nullptr);
}
bool IsComposite() const override {
return true;
}
/**
* Контейнер выполняет свою основную логику особым образом. Он проходит
* рекурсивно через всех своих детей, собирая и суммируя их результаты.
* Поскольку потомки контейнера передают эти вызовы своим потомкам и так
* далее, в результате обходится всё дерево объектов.
*/
std::string Operation() const override {
std::string result;
for (const Component *c : children_) {
if (c == children_.back()) {
result += c->Operation();
} else {
result += c->Operation() + "+";
}
}
return "Branch(" + result + ")";
}
};
/**
* Клиентский код работает со всеми компонентами через базовый интерфейс.
*/
void ClientCode(Component *component) {
// ...
std::cout << "RESULT: " << component->Operation();
// ...
}
/**
* Благодаря тому, что операции управления потомками объявлены в базовом классе
* Компонента, клиентский код может работать как с простыми, так и со сложными
* компонентами, вне зависимости от их конкретных классов.
*/
void ClientCode2(Component *component1, Component *component2) {
// ...
if (component1->IsComposite()) {
component1->Add(component2);
}
std::cout << "RESULT: " << component1->Operation();
// ...
}
/**
* Таким образом, клиентский код может поддерживать простые компоненты-листья...
*/
int main() {
Component *simple = new Leaf;
std::cout << "Client: I've got a simple component:\n";
ClientCode(simple);
std::cout << "\n\n";
/**
* ...а также сложные контейнеры.
*/
Component *tree = new Composite;
Component *branch1 = new Composite;
Component *leaf_1 = new Leaf;
Component *leaf_2 = new Leaf;
Component *leaf_3 = new Leaf;
branch1->Add(leaf_1);
branch1->Add(leaf_2);
Component *branch2 = new Composite;
branch2->Add(leaf_3);
tree->Add(branch1);
tree->Add(branch2);
std::cout << "Client: Now I've got a composite tree:\n";
ClientCode(tree);
std::cout << "\n\n";
std::cout << "Client: I don't need to check the components classes even when managing the tree:\n";
ClientCode2(tree, simple);
std::cout << "\n";
delete simple;
delete tree;
delete branch1;
delete branch2;
delete leaf_1;
delete leaf_2;
delete leaf_3;
return 0;
}
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)