In software engineering, Creational Design Patterns deal with object creation mechanisms, trying to create objects in a manner suitable to the situation. The basic or ordinary form of object creation could result in design problems or added complexity to the design. Builder Design Pattern in C++ solves this specific problem by separating the construction of a complex object from its representation.
By the way, If you haven’t check out my other articles on Creational Design Patterns, then here is the list:
The code snippets you see throughout this series of articles are simplified not sophisticated. So you often see me not using keywords like override, final, public(while inheritance) just to make code compact & consumable(most of the time) in single standard screen size. I also prefer struct instead of class just to save line by not writing “public:” sometimes and also miss virtual destructor, constructor, copy constructor, prefix std::, deleting dynamic memory, intentionally.
I also consider myself a pragmatic person who wants to convey an idea in the simplest way possible rather than the standard way or using Jargons.
Note:
To create/instantiate complex & complicated object piecewise & succinctly by providing an API in a separate entity.
// <p>hello</p>
auto text = "hello";
string output;
output += "<p>";
output += text;
output += "</p>";
printf("<p>%s</p>", text);
// <ul><li>hello</li><li>world</li></ul>
string words[] = {"hello", "world"};
ostringstream oss;
oss << "<ul>";
for (auto w : words)
oss << " <li>" << w << "</li>";
oss << "</ul>";
printf(oss.str().c_str());
class HtmlBuilder;
class HtmlElement {
string m_name;
string m_text;
vector<HtmlElement> m_childs;
constexpr static size_t m_indent_size = 4;
HtmlElement() = default;
HtmlElement(const string &name, const string &text) : m_name(name), m_text(text) {}
friend class HtmlBuilder;
public:
string str(int32_t indent = 0) {
ostringstream oss;
oss << string(m_indent_size * indent, ' ') << "<" << m_name << ">" << endl;
if (m_text.size()) oss << string(m_indent_size * (indent + 1), ' ') << m_text << endl;
for (auto &element : m_childs)
oss << element.str(indent + 1);
oss << string(m_indent_size * indent, ' ') << "</" << m_name << ">" << endl;
return oss.str();
}
static unique_ptr<HtmlBuilder> build(string root_name) { return make_unique<HtmlBuilder>(root_name); }
};
class HtmlBuilder {
HtmlElement m_root;
public:
HtmlBuilder(string root_name) { m_root.m_name = root_name; }
HtmlBuilder *add_child(string child_name, string child_text) {
m_root.m_childs.emplace_back(HtmlElement{child_name, child_text});
return this;
}
string str() { return m_root.str(); }
operator HtmlElement() { return m_root; }
};
int main() {
auto builder = HtmlElement::build("ul");
builder->add_child("li", "hello")->add_child("li", "world");
cout << builder->str() << endl;
return EXIT_SUCCESS;
}
/*
<ul>
<li>
hello
</li>
<li>
world
</li>
</ul>
*/
Person.h
#pragma once
#include <iostream>
using namespace std;
class PersonBuilder;
class Person
{
std::string m_name, m_street_address, m_post_code, m_city; // Personal Detail
std::string m_company_name, m_position, m_annual_income; // Employment Detail
Person(std::string name) : m_name(name) {}
public:
friend class PersonBuilder;
friend ostream& operator<<(ostream& os, const Person& obj);
static PersonBuilder create(std::string name);
};
Person.cpp
#include <iostream>
#include "Person.h"
#include "PersonBuilder.h"
PersonBuilder Person::create(string name) { return PersonBuilder{name}; }
ostream& operator<<(ostream& os, const Person& obj)
{
return os << obj.m_name
<< std::endl
<< "lives : " << std::endl
<< "at " << obj.m_street_address
<< " with postcode " << obj.m_post_code
<< " in " << obj.m_city
<< std::endl
<< "works : " << std::endl
<< "with " << obj.m_company_name
<< " as a " << obj.m_position
<< " earning " << obj.m_annual_income;
}
PersonBuilder.h
#pragma once
#include "Person.h"
class PersonBuilder
{
Person person;
public:
PersonBuilder(string name) : person(name) {}
operator Person() const { return move(person); }
PersonBuilder& lives();
PersonBuilder& at(std::string street_address);
PersonBuilder& with_postcode(std::string post_code);
PersonBuilder& in(std::string city);
PersonBuilder& works();
PersonBuilder& with(string company_name);
PersonBuilder& as_a(string position);
PersonBuilder& earning(string annual_income);
};
PersonBuilder.cpp
#include "PersonBuilder.h"
PersonBuilder& PersonBuilder::lives() { return *this; }
PersonBuilder& PersonBuilder::works() { return *this; }
PersonBuilder& PersonBuilder::with(string company_name) {
person.m_company_name = company_name;
return *this;
}
PersonBuilder& PersonBuilder::as_a(string position) {
person.m_position = position;
return *this;
}
PersonBuilder& PersonBuilder::earning(string annual_income) {
person.m_annual_income = annual_income;
return *this;
}
PersonBuilder& PersonBuilder::at(std::string street_address) {
person.m_street_address = street_address;
return *this;
}
PersonBuilder& PersonBuilder::with_postcode(std::string post_code) {
person.m_post_code = post_code;
return *this;
}
PersonBuilder& PersonBuilder::in(std::string city) {
person.m_city = city;
return *this;
}
Main.cpp
#include <iostream>
#include "Person.h"
#include "PersonBuilder.h"
using namespace std;
int main()
{
Person p = Person::create("John")
.lives()
.at("123 London Road")
.with_postcode("SW1 1GB")
.in("London")
.works()
.with("PragmaSoft")
.as_a("Consultant")
.earning("10e6");
cout << p << endl;
return EXIT_SUCCESS;
}
When should the Builder Design Pattern be used?
Whenever creation of new object requires setting many parameters and some of them (or all of them) are optional.
Why do we need a Builder class when implementing a Builder Design Pattern?
It isn’t necessary but there are some benefits in doing so:
Greatest Advantage of Builder Design Pattern!
More expressive code.
MyClass o = new MyClass(5, 5.5, 'A', var, 1000, obj9, "hello");
What’s the difference between Abstract Factory and Builder Design Pattern?
Previously published at http://www.vishalchovatiya.com/builder-design-pattern-in-modern-cpp/