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;
}

 


Связанные темы