Паттерн Composite (Компонувальник, Дерево). Реалізація структури на C++
Зміст
- 1. Загальні відомості про паттерн Composite. Призначення. Схема паттерну
- 2. Відношення
- 3. Застосування паттерну
- 4. Результати
- 5. Реалізація
- 6. Приклад реалізації схеми паттерну на C++
- Споріднені теми
Пошук на інших ресурсах:
1. Загальні відомості про паттерн Composite. Призначення. Схема паттерну
Паттерн Composite належить до паттернів, які структурують об’єкти. Паттерн призначений для компонування об’єктів в ієрархії деревовидних структур. У вузлах деревовидної структури можуть бути як кінцеві, так і складені об’єкти. Така структура об’єктів називається частина-ціле (part-whole).
Загальна схема паттерну представлена на рисунку 1.
Рисунок 1. Схема паттерну Composite
На рисунку 2 показано приклад складеного об’єкту aComposite. Як видно з рисунку, об’єкт aComposite є складеним а об’єкт aLeaf є індивідуальним (кінцевим).
Рисунок 2. Структура типового складеного об’єкту aComposite
Відповідно до рисунку 1 учасниками паттерну Composite є наступні.
1. Клас Component – реалізує інтерфейс з клієнтом.
Клас забезпечує:
- інтерфейс для об’єктів, які потрібно скомпонувати;
- визначає перелік операцій (методів) за замовчуванням, яка є спільна для всіх класів;
- визначає інтерфейс для успадкованих класів;
- задає інтерфейс для батьківських компонент у рекурсивній структурі з можливістю її реалізації.
2. Клас Leaf – листок.
Цей клас має наступне застосування:
- представляє листові (кінцеві) вузли дерева. Ці вузли не мають нащадків і є вузлами композиції;
- визначає поведінку індивідуальних об’єктів у композиції.
3. Клас Composite – представляє складений об’єкт.
Клас має наступне застосування:
- задає поведінку компонентів, які мають нащадків;
- зберігає компоненти-нащадки;
- реалізує ту частину операцій в класі Component, які керують нащадками.
4. Клас Client – це є клієнтський клас, який оперує об’єктами композиції через інтерфейс Component.
⇑
2. Відношення
Для того, щоб взаємодіяти з об’єктами в деревовидній структурі, клієнти використовують інтерфейс класу Component. Запити робляться як до листових (індивідуальних) об’єктів так і до складених об’єктів. При виконанні запиту до листового об’єкту Leaf цей об’єкт обробляє запит. Якщо ж запит виконується до складеного об’єкту Composite, то він перенаправляє запит своїм нащадкам.
⇑
3. Застосування паттерну
Паттерн компонувальник застосовується у наступних випадках:
- коли потрібно представити ієрархію об’єктів у вигляді частина-ціле;
- коли клієнтам необхідно представити складені та індивідуальні (кінцеві) об’єкти як такі, що трактуються єдиноподібно.
⇑
4. Результати
Для паттерну Composite можна виділити такі позитивні результати, які є перевагами:
- паттерн визначає ієрархії класів, які складаються з простих (індивідуальних, кінцевих) об’єктів а також з складених об’єктів. Прості об’єкти утворюють складені об’єкти;
- архітектура клієнта спрощується. Клієнти можуть однаково працювати як з індивідуальними об’єктами так і з складеними об’єктами. Клієнту невідомо, з яким об’єктом він працює: складеним чи простим. Як наслідок, код клієнта спрощується (немає необхідності писати додатковий код визначення складності того чи іншого об’єкту);
- додавання нових видів компонент є полегшеним. До вже існуючих структур можна автоматично додавати підкласи класів Composite та Leaf.
Недоліком паттерну є те, що важко накладати обмеження на об’єкти. Це стосується випадків коли в складений об’єкт потрібно включати тільки певний набір компонент.
⇑
5. Реалізація
При реалізації паттерну Composite важливо відмітити наступні особливості реалізації:
- необхідність (або відсутність) зберігання явних посилань на своїх батьків (батьківські класи). Якщо є посилання на батьківський клас, то це спрощує навігацію. Також посилання дає можливість реалізувати паттерн Chain of Responsibility (Ланцюжок обов’язків);
- може виникнути необхідність розділення компонент. У цьому випадку компонентам потрібно правильно задати посилання на батьківські класи щоб не виникло неоднозначності;
- додавання якнайбільшої кількості методів у клас Component з метою охоплення більшої функціональності;
- забезпечення безпеки, яке виконується вводом додаткових перевірок у класи що формують складені та примітивні об’єкти.
⇑
6. Приклад реалізації схеми паттерну на C++
У нижченаведеному коді реалізується паттерн Composite, структура якого зображена на рисунку 1 (див. п. 1).
У складеному об’єкті список нащадків реалізовано класом list<T> з стандартної бібліотеки C++.
В ролі клієнта виступає функція main(), яка реалізує дерево об’єктів, зображене на рисунку 3.
Рисунок 3. Дерево об’єктів, яке реалізоване в клієнтському коді
Текст програми на мові C++ наступний.
// Паттерн Composite (Компонувальник) #include <iostream> #include <list> using namespace std; class Composite; // Реалізація структури. class Component { protected: public: // Конструктор Component() { } // чисто віртуальна функція virtual void Operation() = 0; // Метод, що додає елемент - не потрібно нічого робити virtual void Add(Component* _comp) = 0; // Видаляє елемент з дерева - це тільки інтерфейс virtual void Remove(Component* _comp) = 0; // повернути номер нащадка (починаючи з 0) - для класу Сomposite virtual Component* GetChild(int _child) = 0; // Визначає, чи є компонент складеним (Component) virtual Composite* GetComposite() { return nullptr; } }; // Клас Composite - може мати нащадків class Composite :public Component { private: // Список нащадків list<Component*> L; public: // Конструктор - не зберігає дані Composite() : Component() { L.clear(); } // Тут необов'язкова операція void Operation() override { cout << "Composite::Operation()" << endl; } // Додати компонент до списку void Add(Component* _comp) override { L.push_back(_comp); } // Видалення компонента void Remove(Component* _comp) { // Оголосити ітератор на список L list<Component*>::iterator it = L.begin(); // Пошук елементу _comp while (it != L.end()) { if (*it == _comp) break; it++; } // Видалити елемент, якщо він є if (it != L.end()) { L.erase(it); } } // Повертає поточний компонент Composite* GetComposite() override { return this; } // Відобразити список нащадків void PrintChild() { list<Component*>::iterator it = L.begin(); while (it != L.end()) { (*it)->Operation(); it++; } } // Повернути компонент за його номером у вузлі, // номер починається з 0 Component* GetChild(int _child) override { list<Component*>::iterator it = L.begin(); int i = 0; while ((i < _child) && (it != L.end())) { i++; it++; } return *it; } }; // Клас Лист - немає нащадків class Leaf : public Component { private: // Дані класу Leaf string data; public: // Конструктор Leaf(string _data) : data(_data) { } void Operation() override { cout << "Leaf.data = " << data << endl; } private: // Приховати методи Add(), Remove() // тут не потрібно нічого робити void Add(Component* _comp) override { }; // тут також не потрібно нічого робити void Remove(Component* _comp) override { }; // тут не обов'язково щось робити Component* GetChild(int _child) override { return nullptr; } }; void main() { /* Створити дерево Composite1 -----> Leaf1 | |--> Composite2 --> Leaf2 | | | --> Leaf3 | ---> Leaf4 */ // Клієнт Component* client = nullptr; // Створити верхній вузол Component* composite1 = new Composite; // Створити листки Leaf1, Leaf2, Leaf3, Leaf4 Leaf* leaf1 = new Leaf("Leaf1"); Leaf* leaf2 = new Leaf("Leaf2"); Leaf* leaf3 = new Leaf("Leaf3"); Leaf* leaf4 = new Leaf("Leaf4"); // Створити проміжний вузол Composite* composite2 = new Composite; // Додати гілку верхню composite1->Add(leaf1); // Створити середню гілку composite2->Add(leaf2); composite2->Add(leaf3); // Додати середню гілку composite1->Add(composite2); // Додати нижню гілку composite1->Add(leaf4); // Встановити клієнта на composite1 client = composite1; // Відобразити рівень composite1 ((Composite*)client)->PrintChild(); // Відобразити рівень composite2 cout << "------------------" << endl; ((Composite*)composite2)->PrintChild(); // Видалити гілки leaf2 та leaf4 і знову відобразити дерево composite1->Remove(leaf4); composite2->Remove(leaf2); cout << "-------------------------" << endl; cout << "-------------------------" << endl; ((Composite*)composite1)->PrintChild(); cout << "-------------------------" << endl; composite2->PrintChild(); // -------------------------------------- // Дослідження методу GetComposite() Component* resComposite = leaf1->GetComposite(); if (resComposite != nullptr) cout << "leaf1 => Composite" << endl; else cout << "leaf1 => Leaf" << endl; resComposite = composite2->GetComposite(); if (resComposite) cout << "composite1 => Composite" << endl; else cout << "composite1 => Leaf" << endl; // ---------------------------------------- // Звільнити пам'ять під всі компоненти if (leaf1) delete leaf1; if (leaf2) delete leaf2; if (leaf3) delete leaf3; if (leaf4) delete leaf4; if (composite1) delete composite1; if (composite2) delete composite2; }
⇑
Споріднені теми
- Паттерн Adapter. Огляд та дослідження. Приклади реалізації на C++
- Bridge (Міст). Реалізація структури на C++
- Facade (Фасад). Реалізація структури на C++
⇑