DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Design Pattern: What You Need to Know to Improve Your Code Effectively
  • Prototype Pattern in JavaScript
  • Object Relational Behavioral Design Patterns in Java
  • Distribution Design Patterns in Java - Data Transfer Object (DTO) And Remote Facade Design Patterns

Trending

  • The Cypress Edge: Next-Level Testing Strategies for React Developers
  • Event-Driven Architectures: Designing Scalable and Resilient Cloud Solutions
  • Unlocking Data with Language: Real-World Applications of Text-to-SQL Interfaces
  • Comprehensive Guide to Property-Based Testing in Go: Principles and Implementation
  1. DZone
  2. Coding
  3. Languages
  4. Builder Design Pattern in Modern C++

Builder Design Pattern in Modern C++

separating the construction of a complex object from its representation

By 
Vishal Chovatiya user avatar
Vishal Chovatiya
·
Jul. 13, 20 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
13.4K Views

Join the DZone community and get the full member experience.

Join For Free

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:

  1.  Factory.
  2.  Builder.
  3.  Prototype.
  4.  Singleton.

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 and consumable(most of the time) in single standard screen size. I also prefer struct instead of class just to save a 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 based on common jargon.

Note:

  • If you stumbled here directly, then I would suggest you go through What is design pattern? first, even if it is trivial. I believe it will encourage you to explore more on this topic.
  • All of this code you encounter in this series of articles are compiled using C++20 (though I have used Modern C++ features up to C++17 in most cases). So, if you don't have access to the latest compiler you can use https://wandbox.org/, which has preinstalled boost library as well.

Intent

To create/instantiate complex & complicated object piecewise & succinctly by providing an API in a separate entity.

  • Builder Design Pattern is used when we want to construct a complex object. However, we do not want to have a complex constructor member or one that would need many arguments. 
  • The Builder Design Pattern constructs a complex object step-by-step, and the final step will return the object. The process of constructing an object should be generic so that it can be used to create different representations of the same object with the help of a variety of methods.

Life Without Builders

Suppose you have to create the HTML generator using C++ then very naïve way to do it is :

C++
xxxxxxxxxx
1
16
 
1
// <p>hello</p>
2
auto text = "hello";
3
string output;
4
output += "<p>";
5
output += text;
6
output += "</p>";
7
printf("<p>%s</p>", text);
8
9
// <ul><li>hello</li><li>world</li></ul>
10
string words[] = {"hello", "world"};
11
ostringstream oss;
12
oss << "<ul>";
13
for (auto w : words)
14
    oss << "  <li>" << w << "</li>";
15
oss << "</ul>";
16
printf(oss.str().c_str());


A sophisticated dev will create a class with a bunch of constructor argument and method to add a child node. This is a good approach though but it may complicate the object representation.

In general, some objects are simple & can be created in a single constructor call while other objects require a lot of ceremonies to create.

Having an object with 10 constructor arguments is not productive. Instead, we should opt for piecewise construction.

Builder provides an API for constructing an object step-by-step without revealing actual object representation.

Builder Design Pattern Example in Modern C++

C++
xxxxxxxxxx
1
58
 
1
class HtmlBuilder;
2
3
class HtmlElement {
4
    string                      m_name;
5
    string                      m_text;
6
    vector<HtmlElement>         m_childs;
7
    constexpr static size_t     m_indent_size = 4;
8
9
    HtmlElement() = default;
10
    HtmlElement(const string &name, const string &text) : m_name(name), m_text(text) {}
11
    friend class HtmlBuilder;
12
13
public:
14
    string str(int32_t indent = 0) {
15
        ostringstream oss;
16
        oss << string(m_indent_size * indent, ' ') << "<" << m_name << ">" << endl;
17
18
        if (m_text.size()) oss << string(m_indent_size * (indent + 1), ' ') << m_text << endl;
19
20
        for (auto &element : m_childs)
21
            oss << element.str(indent + 1);
22
23
        oss << string(m_indent_size * indent, ' ') << "</" << m_name << ">" << endl;
24
        return oss.str();
25
    }
26
    static unique_ptr<HtmlBuilder> build(string root_name) { return make_unique<HtmlBuilder>(root_name); }
27
};
28
29
class HtmlBuilder {
30
    HtmlElement     m_root;
31
32
public:
33
    HtmlBuilder(string root_name) { m_root.m_name = root_name; }
34
    HtmlBuilder *add_child(string child_name, string child_text) {
35
        m_root.m_childs.emplace_back(HtmlElement{child_name, child_text});
36
        return this;
37
    }
38
    string str() { return m_root.str(); }
39
    operator HtmlElement() { return m_root; }
40
};
41
42
int main() {
43
    auto builder = HtmlElement::build("ul");
44
    builder->add_child("li", "hello")->add_child("li", "world");
45
46
    cout << builder->str() << endl;
47
    return EXIT_SUCCESS;
48
}
49
/*
50
<ul>
51
    <li>
52
        hello
53
    </li>
54
    <li>
55
        world
56
    </li>
57
</ul>
58
*/


We are forcing users here to use a builder by making data members of HtmlElements private.

As you can see, we have declared the HtmlBuilder and HtmlElement in the same file. To do so, we need forward declaration i.e. class HtmlBuilder; as it is an incomplete type. And we cannot create the object of incomplete type before compiler parses its actual declaration. The reason is simple — the compiler needs the size of an object to allocate memory for it. Hence pointer is only way around so we have taken unique_ptr.

Sophisticated and Fluent Builder Design Pattern Example

Following is the more sophisticated example of the Builder Design Pattern organized in four different files(i.e. Person.h, Person.cpp, PersonBuilder.h PersonBuilder.cpp). 

Person.h

C++
xxxxxxxxxx
1
18
 
1
#pragma once
2
#include <iostream>
3
using namespace std;
4
5
class PersonBuilder;
6
7
class Person
8
{
9
    std::string m_name, m_street_address, m_post_code, m_city;  // Personal Detail
10
    std::string m_company_name, m_position, m_annual_income;    // Employment Detail
11
12
    Person(std::string name) : m_name(name) {}
13
14
public:
15
    friend class PersonBuilder;
16
    friend ostream& operator<<(ostream&  os, const Person& obj);
17
    static PersonBuilder create(std::string name);
18
};


Person.cpp

C++
xxxxxxxxxx
1
20
 
1
#include <iostream>
2
#include "Person.h"
3
#include "PersonBuilder.h"
4
5
PersonBuilder Person::create(string name) { return PersonBuilder{name}; }
6
7
ostream& operator<<(ostream& os, const Person& obj)
8
{
9
    return os << obj.m_name
10
              << std::endl
11
              << "lives : " << std::endl
12
              << "at " << obj.m_street_address
13
              << " with postcode " << obj.m_post_code
14
              << " in " << obj.m_city
15
              << std::endl
16
              << "works : " << std::endl
17
              << "with " << obj.m_company_name
18
              << " as a " << obj.m_position
19
              << " earning " << obj.m_annual_income;
20
}


As you can see from the above example Person may have many details like Personal & Professional. And so does the count of data members. 

In our case, there are 7 data members. Having a single class for all the actions needed to create a Person through constructor might make our class bloated and lose its original purpose. Moreover, the library user needs to take care of all those constructor parameters sequence.

PersonBuilder.h

C++
xxxxxxxxxx
1
21
 
1
#pragma once
2
#include "Person.h"
3
4
class PersonBuilder
5
{
6
    Person person;
7
8
public:
9
    PersonBuilder(string name) : person(name) {}
10
11
    operator Person() const { return move(person); }
12
13
    PersonBuilder&  lives();
14
    PersonBuilder&  at(std::string street_address);
15
    PersonBuilder&  with_postcode(std::string post_code);
16
    PersonBuilder&  in(std::string city);
17
    PersonBuilder&  works();
18
    PersonBuilder&  with(string company_name);
19
    PersonBuilder&  as_a(string position);
20
    PersonBuilder&  earning(string annual_income);
21
};


PersonBuilder.cpp

C++
xxxxxxxxxx
1
35
 
1
#include "PersonBuilder.h"
2
3
PersonBuilder&  PersonBuilder::lives() { return *this; }
4
5
PersonBuilder&  PersonBuilder::works() { return *this; }
6
7
PersonBuilder&  PersonBuilder::with(string company_name) {
8
    person.m_company_name = company_name; 
9
    return *this;
10
}
11
12
PersonBuilder&  PersonBuilder::as_a(string position) {
13
    person.m_position = position; 
14
    return *this;
15
}
16
17
PersonBuilder&  PersonBuilder::earning(string annual_income) {
18
    person.m_annual_income = annual_income; 
19
    return *this;
20
}
21
22
PersonBuilder&  PersonBuilder::at(std::string street_address) {
23
    person.m_street_address = street_address; 
24
    return *this;
25
}
26
27
PersonBuilder&  PersonBuilder::with_postcode(std::string post_code) {
28
    person.m_post_code = post_code; 
29
    return *this;
30
}
31
32
PersonBuilder&  PersonBuilder::in(std::string city) {
33
    person.m_city = city; 
34
    return *this;
35
}


Rather stuffing all those construction related APIs into Person, we can delegate that task to separate entity i.e. PersonBuilder.

Main.cpp

C++
xxxxxxxxxx
1
20
 
1
#include <iostream>
2
#include "Person.h"
3
#include "PersonBuilder.h"
4
using namespace std;
5
6
int main()
7
{
8
    Person p = Person::create("John")
9
                                .lives()
10
                                    .at("123 London Road")
11
                                    .with_postcode("SW1 1GB")
12
                                    .in("London")
13
                                .works()
14
                                    .with("PragmaSoft")
15
                                    .as_a("Consultant")
16
                                    .earning("10e6");
17
18
    cout << p << endl;
19
    return EXIT_SUCCESS;
20
}


Isn't the above construction looks more intuitive, natural, and plain English?

  • If you are concerned about blank methods like lives() and works(), then do not worry, it will be eliminated in optimization.
  • You can also observe that we are forcing user's to use builder rather than constructor by making the constructor private & exposing only create(std::string name) API.
  • Do not over complicate things by designing an interface or abstract classes unless you need it. I have seen this in many Builder Design Pattern Examples on Web. 

Benefits of Builder Design Pattern

  • The number of lines of code increases at least to double in builder pattern. But the effort pays off in terms of design flexibility, fewer or no parameters to the constructor and much more readable code. 
  • Builder Design Pattern also helps in minimizing the number of parameters in constructor & thus there is no need to pass in null for optional parameters to the constructor.
  • Immutable objects can be built without much complex logic in the object building process.
  • Segregating construction from object representation makes the object representation slice & precise. Having a builder entity separate provides the flexibility of creating & instantiating different objects representations.

Summary by FAQs

When should the Builder Design Pattern be used? 

Whenever the 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:
- The concern of building object should be in the separate entity as per SRP.
- The original object would not be bloated.
- Easy & maintainable code.
- Testing & understanding a constructor with many input arguments gets exponentially more complicated.

  Greatest Advantage of Builder Deisgn Pattern! 

More expressive code.
MyClass o = new MyClass(5, 5.5, 'A', var, 1000, obj9, "hello");
- Instead
MyClass o = MyClass.builder().a(5).b(5.5).c('A').d(var).e(1000).f(obj9)
.g("hello");
- You can see which data member is being assigned by what & even change the order of assignment.


If you are interested in receiving such articles directly into your inbox, please sign up to my newsletter. 
Design Object (computer science)

Published at DZone with permission of Vishal Chovatiya. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Design Pattern: What You Need to Know to Improve Your Code Effectively
  • Prototype Pattern in JavaScript
  • Object Relational Behavioral Design Patterns in Java
  • Distribution Design Patterns in Java - Data Transfer Object (DTO) And Remote Facade Design Patterns

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!