Будівник (шаблон проєктування)
Будівельник (англ. Builder) — шаблон проєктування, відноситься до класу твірних шаблонів. На відміну від шаблону абстрактної фабрики і фабричного методу, ціль яких є застосування поліморфізму, задачею шаблону будівельника є забезпечення реалізації анти-шаблону телескопічного (багатоступеневого) конструювання. Анти-шаблон «телескопічний конструктор» коли різна комбінація параметрів конструктора призводить до появи експоненційної множини конструкторів. Замість того, щоб використовувати набір конструкторів, використовують шаблон будівельник, згідно якого використовують інший об'єкт (будівельник), що отримує на вхід вхідні параметри по одному, крок за кроком, і повертає за один прохід результуючий створений об'єкт.
Призначення
Відокремлює конструювання складного об'єкта від його подання, таким чином у результаті одного й того ж процесу конструювання можуть бути отримані різні подання.
Мотивація
Вам запропоновано створити систему планування екскурсій по Паттернленду — новому парку розваг. Гості парку можуть вибрати готель, замовити квитки на атракціони, зарезервувати місця в ресторані і навіть замовити спеціальні заходи. Поїздки можуть відрізнятися за кількістю днів і складу розважальної програми. Наприклад, місцевий житель не бажає зупинятися у готелі, але хоче замовити обід та спеціальні заходи. Другий гість прилітає літаком і йому необхідно забронювати номер в готелі, столик в ресторані і квитки на заходи. Таким чином, нам потрібна гнучка структура даних, яка може представляти програму поселення з усіма можливими варіаціями; крім того, побудова програми може складатися з декількох кроків.
Застосування
Слід використовувати шаблон Будівельник коли:
- алгоритм створення складного об'єкта не повинен залежати від того, з яких частин складається об'єкт та як вони стикуються поміж собою;
- процес конструювання повинен забезпечити різні подання об'єкта, що конструюється.
Структура
- Builder — будівельник:
- визначає абстрактний інтерфейс для створення частин об'єкта Product;
- ConcreteBuilder — конкретний будівельник:
- конструює та збирає докупи частини продукту шляхом реалізації інтерфейсу Builder;
- визначає подання, що створюється, та слідкує на ним;
- надає інтерфейс для доступу до продукту;
- Director — керівник:
- конструює об'єкт, користуючись інтерфейсом Builder;
- Product — продукт:
- подає складний конструйований об'єкт. ConcreteBuilder будує внутрішнє подання продукту та визначає процес його зборки;
- вносить класи, що визначають складені частини, у тому числі інтерфейси для зборки кінцевого результату з частин.
Переваги
- дозволяє змінювати внутрішнє представлення продукту;
- ізолює код, який реалізує конструювання та подання;
- дає більш тонкий контроль над процесом конструювання.
Недоліки
- Потрібно створити окремий ConcreteBuilder для кожного окремого продукту.
Відносини
- клієнт створює об'єкт-управитель Director та конфігурує його потрібним об'єктом-будівником Builder;
- управитель повідомляє будівельника про те, що потрібно побудувати наступну частину продукту;
- будівельник оброблює запити управителя та додає нові частини до продукту;
- клієнт забирає продукт у будівельника.
Реалізація
Приклад С++
#include <iostream>
#include <string>
using namespace std;
// Builder
class CReportBuilder
{
public:
virtual ~CReportBuilder() {};
virtual void CreateHeader() = 0;
virtual void CreateBody() = 0;
virtual void CreateFooter() = 0;
virtual void Print()
{
cout << m_strReport << endl;
};
protected:
string m_strReport;
};
// Конкретний будівельник 1
struct CTextReportBuilder : public CReportBuilder
{
virtual ~CTextReportBuilder() {};
virtual void CreateHeader()
{
m_strReport.append("SIMPLE REPORT'S HEADER \n");
};
virtual void CreateBody()
{
m_strReport.append("\n\"Simple Report's Main Text\"\n \n");
};
virtual void CreateFooter()
{
m_strReport.append("Simple Report's Footer \n");
};
};
// Конкретний будівельник 2
struct CHTMLReportBuilder : public CReportBuilder
{
virtual ~CHTMLReportBuilder() {};
virtual void CreateHeader()
{
m_strReport.append("<HTML><BODY> \n");
m_strReport.append("<H1>HTML REPORT'S HEADER</H1> \n");
};
virtual void CreateBody()
{
m_strReport.append("<p>\"HTML Report's Main Text\"</p> \n");
};
virtual void CreateFooter()
{
m_strReport.append("<p><i>HTML Report's Footer</i></p> \n");
m_strReport.append("</BODY></HTML>");
};
};
// Director. Створює продукт використовуючи інтерфейс Будівельника
void CreateReport(CReportBuilder& report)
{
report.CreateHeader();
report.CreateBody();
report.CreateFooter();
};
void main()
{
CHTMLReportBuilder Report; // Product
CreateReport(Report);
Report.Print();
}
Приклад Swift
class DeathStarBuilder {
var x: Double?
var y: Double?
var z: Double?
typealias BuilderClosure = (DeathStarBuilder) -> ()
init(buildClosure: BuilderClosure) {
buildClosure(self)
}
}
struct DeathStar : CustomStringConvertible {
let x: Double
let y: Double
let z: Double
init?(builder: DeathStarBuilder) {
if let x = builder.x, y = builder.y, z = builder.z {
self.x = x
self.y = y
self.z = z
} else {
return nil
}
}
var description:String {
return "Death Star at (x:\(x) y:\(y) z:\(z))"
}
}
let empire = DeathStarBuilder { builder in
builder.x = 0.1
builder.y = 0.2
builder.z = 0.3
}
let deathStar = DeathStar(builder:empire)
Джерела
Література
Алан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — ISBN 0-201-71594-5.