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 процес конструювання продукту відбувається більш гнучко.

 


Зв’язані теми