Patterns. Паттерн Composite (Компонувальник). Дерево. Реалізація на C++

Паттерн Composite (Компонувальник, Дерево). Реалізація структури на C++


Зміст


Пошук на інших ресурсах:

1. Загальні відомості про паттерн Composite. Призначення. Схема паттерну

Паттерн Composite належить до паттернів, які структурують об’єкти. Паттерн призначений для компонування об’єктів в ієрархії деревовидних структур. У вузлах деревовидної структури можуть бути як кінцеві, так і складені об’єкти. Така структура об’єктів називається частина-ціле (part-whole).

Загальна схема паттерну представлена на рисунку 1.

Паттерн Composite (Компонувальник). Структура

Рисунок 1. Схема паттерну Composite

На рисунку 2 показано приклад складеного об’єкту aComposite. Як видно з рисунку, об’єкт aComposite є складеним а об’єкт aLeaf є індивідуальним (кінцевим).

Паттерн Composite (Компонувальник). Структура типового складеного об’єкту aComposite

Рисунок 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.

Паттерн Composite (Компонувальник). Дерево об’єктів, яке реалізоване в клієнтському коді

Рисунок 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;
}

 


Споріднені теми