Порождающий паттерн. Строитель.



Строитель

Паттерн "Строитель" отделяет алгоритм поэтапного конструирования сложного объекта от его внешнего представления так, что с помощью одного и того же алгоритма можно получать разные представления этого продукта.
Паттерн позволяет строить сложные объекты, процесс создания которых состоит из нескольких этапов. Класс "строитель" задает абстрактный интерфейс для создания частей объекта, а его подклассы ("конкретные строители") реализуют этот интерфейс, определяют порядок сборки и предоставляют интерфейс к создаваемому объекту.
Клиент для создания объекта создает объект-распорядитель, конфигурируя его конкретным строителем. Распорядитель конструирует объект из частей, пользуясь интерфейсом строителя и вызывая методы конкретного строителя.
Поскольку продукт конструируется через абстрактный интерфейс, то для изменения внутреннего представления достаточно всего лишь определить новый вид конкретного строителя. Данный паттерн улучшает модульность, инкапсулируя способ конструирования и представления сложного объекта. Клиентам ничего не надо знать о классах, определяющих внутреннюю структуру продукта.
В отличии от порождающих паттернов, которые сразу конструируют весь объект целиком, строитель делает это шаг за шагом под управлением распорядителя, что дает более тонкий контроль над процессом конструирования.
Данный паттерн очень тесно переплетается с паттерном "абстрактная фабрика". Основное различие в том, что "строитель" внутри себя содержит все сложные операции по созданию объекта.

Фабрика отличается тем, что фабрика дает готовый объект, строитель же, строит поэтапно объект, в эти этапы кроме всего прочего, могут быть включены - подсчет количества созданных объектов, загрузка данных, запись(чтение) в(из) базу(ы) данных... 
Фабрика - вы пришли в салон по продаже автомобилей(конечный продукт автомобиль), выбрали какой автомобиль, и получили его.
Строитель - вы пришли на завод, определились с тем какой вы хотите автомобиль, начинаете его упорно поэтапно его создавать, контролируя каждый этап сборки, ведете учет потраченных ресурсов, в случае нехватки чего-то есть возможность заменить на что-то, и т.д. Клиент может собирать автомобили, работая со строителем напрямую. Но с другой стороны, он может поручить это дело распорядителю(директору). Это объект, который знает какие шаги строителя нужно вызвать, чтобы получить несколько самых популярных конфигураций автомобилей.

Достоинства паттерна "Строитель"
  • Возможность контролировать процесс создания сложного продукта
  • Возможность получения разных представлений некоторых данных
  • Позволяет использовать один и тот же код для создания различных продуктов
  • Изолирует сложный код сборки продукта от его основной бизнес-логики
Недостатки паттерна "Строитель"
  • ConcreteBuilder и создаваемый им продукт жестко связаны между собой, поэтому при внесеннии изменений в класс продукта скорее всего придется соотвествующим образом изменять и класс ConcreteBuilder.
  • Усложняет код программы за счёт дополнительных классов.
Пример:
#include <iostream>
#include <string>
#include <memory>

struct Base
{
  int price;
};

struct Wheel : Base
{
  int size;
};

struct Engine : Base
{
  int power;
};


struct Body : Base
{
  std::string type;
};

struct Car
{
  void print_info()
  {
    std::cout << "Model name   : " << mName << std::endl;
    std::cout << "Body type    : " << mBody->type << std::endl;
    std::cout << "Engine power : " << mEngine->power << std::endl;
    std::cout << "Wheel size   : " << mWheel[0]->size << std::endl;

    std::cout << "Common price : " << mWheel[0]->price * 4
                                      + mEngine->price
                                      + mBody->price
                                   << std::endl;

  }
  std::string mName;
  std::unique_ptr<Wheel>  mWheel[4];
  std::unique_ptr<Engine> mEngine;
  std::unique_ptr<Body>   mBody;
};

struct Builder
{
  virtual std::unique_ptr<Wheel>  make_wheel() = 0;
  virtual std::unique_ptr<Engine> make_engine() = 0;
  virtual std::unique_ptr<Body>   make_body() = 0;
  std::string mName;
};

struct Director
{
  void setBuilder(std::unique_ptr<Builder> aBuilder)
  {
    mBuilder = std::move(aBuilder);
  }

  std::unique_ptr<Car> makeCar()
  {
    std::unique_ptr<Car> res = std::make_unique<Car>();

    res->mName     = mBuilder->mName;
    res->mWheel[0] = mBuilder->make_wheel();
    res->mWheel[1] = mBuilder->make_wheel();
    res->mWheel[2] = mBuilder->make_wheel();
    res->mWheel[3] = mBuilder->make_wheel();
    res->mBody     = mBuilder->make_body();
    res->mEngine   = mBuilder->make_engine();

    return res;
  }

  std::unique_ptr<Builder> mBuilder;
};

struct AudiQ7Diesel : Builder
{
  AudiQ7Diesel()
  {
    mName = "Audi Q7 Diesel";
  }

  std::unique_ptr<Wheel> make_wheel() override
  {
    std::unique_ptr<Wheel> wheel = std::make_unique<Wheel>();
    wheel->price = 100;
    wheel->size  = 19;
    return wheel;
  }

  std::unique_ptr<Engine> make_engine() override
  {
    std::unique_ptr<Engine> engine = std::make_unique<Engine>();
    engine->price = 2000;
    engine->power = 500;
    return engine;
  }

  std::unique_ptr<Body> make_body() override
  {
    std::unique_ptr<Body> body = std::make_unique<Body>();
    body->price = 3000;
    body->type  = "SUV";
    return body;
  }
};

struct AudiTTGasoline : Builder
{
  AudiTTGasoline()
  {
    mName = "Audi TT Gasoline";
  }

  std::unique_ptr<Wheel> make_wheel() override
  {
    std::unique_ptr<Wheel> wheel = std::make_unique<Wheel>();
    wheel->price = 150;
    wheel->size  = 17;
    return wheel;
  }

  std::unique_ptr<Engine> make_engine() override
  {
    std::unique_ptr<Engine> engine = std::make_unique<Engine>();
    engine->price = 2500;
    engine->power = 310;
    return engine;
  }

  std::unique_ptr<Body> make_body() override
  {
    std::unique_ptr<Body> body = std::make_unique<Body>();
    body->price = 2500;
    body->type  = "coupe";
    return body;
  }
};

int main()
{
  Director director;
  director.setBuilder(std::make_unique<AudiQ7Diesel>());

  std::unique_ptr<Car> audiQ7(std::make_unique<Car>());
  audiQ7 = director.makeCar();
  audiQ7->print_info();

  std::cout << std::endl;
  std::cout << std::endl;

  director.setBuilder(std::make_unique<AudiTTGasoline>());
  std::unique_ptr<Car> audiTT(std::make_unique<Car>());
  audiTT = director.makeCar();
  audiTT->print_info();

  return 0;
}



Комментарии