Patterns. Паттерн Builder. Обзор и исследование. Реализация на C++

Паттерн Builder. Обзор и исследование. Реализация на C++


Содержание


Поиск на других ресурсах:




1. Паттерн Builder. Назначение. Общие сведения

Паттерн Builder принадлежит к порождающим паттернам и используется для порождения объектов.

Необходимость использования паттерна Builder в программе возникает в случаях, когда нужно добавлять новые возможности без существенного изменения кода. Под возможностями понимаются дополнительные преобразования, которые генерируют конечный продукт (объект). В паттерне Builder представление объекта отделяется от его конструирования (построения). При этом, для конкретной конструкции получаются разные представления.

 

2. Структура паттерна Builder. Рисунок

Наиболее часто в литературе встречается следующая структура паттерна Builder (рисунок 1).

 

Структура паттерна Builder

Рисунок 1. Структура паттерна Builder

На вышеприведенном рисунке 1, структура описывает следующее:

  • класс ConcreteBuilder, порождающий объект Product – это есть конкретный строитель. Класс реализует метод BuildPart() интерфейса Builder. Также класс содержит метод GetResult(), возвращающий указатель (ссылку) на сконструированный объект (продукт). Таким образом класс ConcreteBuilder создает некоторое представление в программе;
  • интерфейс (абстрактный класс) Builder, в котором указывается метод или несколько методов, которые предоставляются распорядителю (класс   Director), который вызывает процесс конструирования объекта;
  • класс Director (распорядитель) – получает ссылку (указатель) builder на интерфейс Builder. Это есть типичная форма агрегации. С помощью этой ссылки класс может вызывать экземпляр класса ConcreteBuilder. Если классов строителей (наподобие ConcreteBuilder) несколько, то есть возможность выбирать соответствующие методы построения объектов этих классов (продуктов);
  • Product – это есть конкретный продукт (объект) класса ConcreteBuilder, который возвращается методом GetResult().

 

3. Использование паттерна Builder клиентом. Диаграмма взаимодействия

При использовании паттерна Builder клиент имеет в распоряжении :

  • класс распорядителя Director;
  • интерфейс (абстрактный класс, класс) Builder.

Последовательность шагов клиента следующая.

  1. Создать объект класса типа ConcreteBuilder (создать строителя).
  2. Создать экземпляр класса распорядителя Director и сконфигурировать его экземпляром класса ConcreteBuilder.
  3. В классе распорядителе Director вызвать методы (запросы), которые обращаются к методам класса строителя (ConcreteBuilder) с запросом построить продукт (объект). В результате, строитель (ConcreteBuilder) создает объект типа Product.
  4. Клиент получает продукт у строителя и использует его.

На рисунке 2 изображена диаграмма взаимодействия клиента с распорядителем (Director) и строителем (ConcreteBuilder).

Паттерн Builder. Диаграмма взаимодействия

Рисунок 2. Паттерн Builder. Диаграмма взаимодействия

 

4. Пример реализации паттерна Builder на C++

В примере демонстрируется использование паттерна Builder для генерирования объектов класса Product. Названия классов соответствуют рисунку 1. Условно принято, что класс Product содержит код, который состоит из двух целочисленных частей: part1, part2. Эти части нужно построить используя подход паттерна Builder. В коде клиента (функция main()) получается экземпляр класса Product со значениями частей 25 и 777. Затем делается проверка полученного результата.

На рисунке 3 изображена структура, которая используется для решения данной задачи.

Паттерн Builder. Структура решения задачи

Рисунок 3. Паттерн Builder. Структура решения задачи

Текст решения задачи на языке C++ (Console Application), который соответствует рисунку 3, следующий.

#include <iostream>
using namespace std;

// Класс, который есть продуктом
class Product
{
public:
  int part1; // часть 1
  int part2; // часть 2
};

// Класс, который реализует интерфейс с клиентом
class Builder
{
public:
  virtual void CreateProduct() { }
  virtual void BuildPart1(int part1) { }
  virtual void BuildPart2(int part2) { }
  virtual Product* GetResult() { return nullptr; }
};

// Класс, который есть конкретным строителем
class ConcreteBuilder : public Builder
{
private:
  Product* currentBuilder;

public:
  // Конструктор
  ConcreteBuilder()
  {
    currentBuilder = nullptr;
  }

  // Реализация виртуальных методов
  virtual void CreateProduct()
  {
    cout << "ConcreteBuilder::CreateProduct()" << endl;
    currentBuilder = new Product();
  }

  // Построить часть 1
  virtual void BuildPart1(int part1)
  {
    cout << "ConcreteBuilder::currentBuilder->part1 = " << part1 << endl;
    currentBuilder->part1 = part1;
  }

  // Построить часть 2
  virtual void BuildPart2(int part2)
  {
    cout << "ConcreteBuilder::currentBuilder->part2 = " << part2 << endl;
    currentBuilder->part2 = part2;
  }

  // Метод, возвращающий продукт для клиента
  virtual Product* GetResult()
  {
    return currentBuilder;
  }

  // Деструктор
  ~ConcreteBuilder()
  {
    if (currentBuilder != nullptr)
      delete currentBuilder;
  }
};

// Класс - распорядитель
class Director
{
public:
  // Метод, конструирующий части
  void Construct(Builder& builder)
  {
    // 1. Создать продукт
    builder.CreateProduct();

    // 2. Построить часть 1
    builder.BuildPart1(25);

    // 3. Построить часть 2
    builder.BuildPart2(777);
  }
};

void main()
{
  // Функция main() в данном случае выступает клиентом
  // 1. Объявить указатель на продукт, который нужно получить
  Product* product;

  // 2. Создать конкретный экземпляр класса ConcreteBuilder и заполнить значениями
  ConcreteBuilder B;

  // 3. Создать класс-распорядитель и сконфигурировать его продуктом B
  Director D;
  D.Construct(B); // сконфигурировать

  // 4. После конфигурирования, передать созданный продукт клиенту
  product = B.GetResult();

  // 5. Вывести значение продукта для контроля
  cout << "product->part1 = " << product->part1 << endl;
  cout << "product->part2 = " << product->part2 << endl;
}

После выполнения программа выдаст следующий результат

ConcreteBuilder::CreateProduct()
ConcreteBuilder::currentBuilder->part1 = 25
ConcreteBuilder::currentBuilder->part2 = 777
product->part1 = 25
product->part2 = 777

 

5. Особенности применения паттерна Builder

В паттерне Builder можно выделить следующие характерные особенности применения:

  • паттерн построен таким образом, что позволяет изменять внутреннее представление продукта. Можно выполнять разные варианты построения результирующего объекта;
  • в паттерне строго отделяется код построения объекта от кода представления сложного объекта;
  • паттерн Builder улучшает модульность. Это значит, что способ конструирования и представление сложного объекта инкапсулируются. Клиент не знает об особенностях реализации классов, которые определяют внутреннюю структуру продукта;
  • есть возможность без изменения структуры основного кода добавлять новых строителей (ConcreteBuilder2, ConcreteBuilder3 и т.д.);
  • распорядитель (Director) может строить разные варианты продукта из одних и тех же частей. Также может быть несколько распорядителей (Director2, Director3 и т.д.);
  • процесс конструирования продукта может быть разбит на части (BuildPart1(), BuildPart2(), и т.д.) под управлением распорядителя (Director). В сравнении с другими порождающими паттернами в паттерне Builder процесс конструирования продукта происходит более гибко.

 


Related topics