Builder Design Pattern – Easy explanation with real-life examples

Our objective with the Design Pattern post series is to make sure you’re able to understand each pattern easily regardless of how simple or complex it is! And with the Builder Design Pattern, we may have met our match. This is one of those patterns that could potentially take a bit of time and reading to understand, but that’s what we’re here for!

It’s a creational design pattern, and it is especially used whenever we are building a very complex object which requires a lot of configurations – and keep in mind that these 2 keywords are critical for understanding this pattern.

Let us try to understand this with an example.

Let’s say we need to build a vehicle. A vehicle could be anything from a car to a bus to a truck to a two-wheeler and so on . It’s a complex object because it contains a lot of different components like an engine, brakes, speedometer, tyres, interior etc. Now these are some basic components that each and every vehicle would require for its optimum functionality but the construction and configuration of these parts may differ from vehicle to vehicle, from company to company. Lastly, we need to assemble all these parts to make this complex object that we call a vehicle.

Similarly, let’s take another easy example of building a house. Now a house is nothing but a shelter with some key features (interior, roof, floor) which makes it a house. But there are a lot of different types of houses (igloo, houseboat, penthouse), and in order to build a house, we need to simply configure the different parts involved in building that specific type of house.

So basically, we can classify a number of objects that we use in our day-to-day life as a complex object and the process of building that object can be represented using the Builder design pattern.

Now let’s come to the example we’ll be using to build our code.

Problem Statement

Let’s take a Desktop as a complex object and let’s see how we can represent it with this pattern.

First, in order to build a complex object, we need to identify the key components used in making this complex object. For instance, for a desktop we have a monitor, a mouse, a keyboard, a processor, the RAM and so much more. We have to first build all these parts and assemble them into a desktop.

In order to now make this desktop object, you might think it’s just a matter of passing all the built parts to a constructor. But suppose we want to change the type of RAM at some point, or maybe we only want to pass a few parts to the constructor and default the rest. Should we then write different constructors with different default configurations, like say one constructor only takes the monitor and the mouse, another only takes the type of touchscreen we want, and so on?

Of course not! We’re going to end up with code that is neither flexible nor readable.

If we jump right into our code, we might feel slightly overwhelmed because there’s quite some work involved. So first let’s understand the basic blocks of this pattern with the help of a diagram.

UML diagram

There are 4 different components of this pattern :

  1. Product: The complex object we are going to create. In our case, this is the desktop.
  2. Builder: This is the interface which will list all the functions that need to be implemented by the concrete builder in order to build the product. For our example, we will have a Desktop Builder. Then what are our concrete builders?
  3. Concrete Builder: This component will actually build the components told to it by the builder, and have to do so through implementing the functions listed in the builder. Let’s say we have 2 concrete builders – a Dell Desktop Builder and an HP Desktop Builder – and both of these will perform actions like buildMonitor(), buildMouse(), buildKeyboard() to actually build the configured parts of the desktop.
  4. Director: This component is responsible for the actual generation of the product. So it will basically state the order in which we should perform all the necessary functions to come up with the product. So it might first instruct the building of the monitor, then the keyboard and so on. In our earlier example of building a house, the Civil Engineer will be our director who will tell the construction authorities the order in which to perform the construction.

Strictly speaking, some people implement the director while some people call the user/client that uses the Builder design pattern as the Director. So we could label this component optional since the same code could be written in main() to make it the director. For the sake of understanding the pattern thoroughly, we’re going to be creating a separate director component in our code.

Implementation

  1. Creating the product  – Desktop

The desktop class will contain all the parts that a desktop contains, and these parts will be private. We will also require some public setters to initialize (or configure) them independently. We’ve set them all as string type to keep things simple but each part could be an object by itself. We’ll also add one more function showSpecs() to display the output after building the desktop to confirm we’ve got the right parts.

Here are the .hpp and .cpp files containing everything we just discussed.

#ifndef DESKTOP_H
#define DESKTOP_H

#include <string>
using namespace std;

class Desktop {
	string monitor;
	string keyboard;
	string mouse;
	string speaker;
	string ram;
	string processor;
	string motherBoard;

public:
	void setMonitor(string pMonitor);
	void setKeyBoard(string pKeyBoard);
	void setMouse(string pMouse);
	void setSpeaker(string pSpeaker);
	void setRam(string pRam);
	void setProcessor(string pProcessor);
	void setMotherBoard(string pMotherBoard);
	void showSpecs();
};

#endif // !DESKTOP_H
#include "desktop.h"
#include <iostream>
	void Desktop::setMonitor(string pMonitor) {
		monitor = pMonitor;
	}
	void Desktop::setKeyBoard(string pKeyBoard) {
		keyboard = pKeyBoard;
	}
	void Desktop::setMouse(string pMouse) {
		mouse = pMouse;
	}
	void Desktop::setSpeaker(string pSpeaker) {
		speaker = pSpeaker;
	}
	void Desktop::setRam(string pRam) {
		ram = pRam;
	}
	void Desktop::setProcessor(string pProcessor) {
		processor = pProcessor;
	}
	void Desktop::setMotherBoard(string pMotherBoard) {
		motherBoard = pMotherBoard;
	}
	void Desktop::showSpecs() {
		cout << "------------------------------------------------------------" << endl;
		cout << "Monitor " << monitor << endl;
		cout << "KeyBoard " << keyboard << endl;
		cout << "Mouse " << mouse << endl;
		cout << "Speaker " << speaker << endl;
		cout << "RAM " << ram << endl;
		cout << "Processor " << processor << endl;
		cout << "Mother Board " << motherBoard << endl;
		cout << "------------------------------------------------------------" << endl;
	}
  1. Creating the builder – Desktop Builder

As we mentioned above, the builder acts as a basic interface which will tell the concrete builder what components to build. So we’ll declare it as an abstract class in our header file, containing the product as a protected member.

Why protected?

Because the builder will be inherited by the concrete builder and it will need to access the product to build the components.

In order to include the product, we’ll also need to include the desktop header file here.

We’ll go ahead and initialise this desktop product in the constructor of the desktop builder itself. So that as soon as a builder is created, a product is created corresponding to it. But since this is a simple product, a simple desktop, none of the parts have been built/configured yet so we will need to add all of those parts which will be implemented by our concrete builders.

You’ll notice we’re making all our builder functions purely virtual by equating to 0, which means that all the concrete builders that inherit from this desktop builder will have to implement these functions. So our desktop will not be created as is, it will be accompanied by the creation of all these separate parts.

Since the desktop product is here, we can have a getter function in the builder itself. Some people choose to place their desktop getters in the concrete builders instead. While that’s not incorrect, since in our case we are only creating different types of desktops, we can place our getter here itself. But what if we were building a car and a scooter in our concrete builders? We’d have different return types despite inheriting from a Vehicle, so we could have a vehicle getter but if we didn’t have that, we’d have to write our getters in the concrete builders themselves.

So just note that even though there are minute differences in how people implement this pattern, it’s perfectly alright to place our getter in different locations.

Let’s also keep our getter virtual so it can be overridden if required.

#ifndef  DESKTOPBUILDER_H
#define	DESKTOPBUILDER_H

#include "desktop.h"

class DesktopBuilder {
protected:
	Desktop* desktop;
public:
	DesktopBuilder() {
		desktop = new Desktop();
	}
	virtual void buildMonitor() = 0;
	virtual void buildKeyBoard() = 0;
	virtual void buildMouse() = 0;
	virtual void buildSpeaker() = 0;
	virtual void buildRam() = 0;
	virtual void buildProcessor() = 0;
	virtual void buildMotherBoard() = 0;
	virtual Desktop* getDesktop() {
		return desktop;
	}
};

#endif // ! DESKTOPBUILDER_H

 

  1. Creating the concrete builder

All we have to do to create the Dell and HP desktop builders is to implement the pure virtual functions in the builder class. In both our concrete builder header files, we’ll include the desktop builder header since our classes will be inheriting from the desktop builder class.

We’ll create the DellDesktopBuilder and HPDesktopBuilder classes which will inherit from DesktopBuilder and implement all the functions mentioned before. To build all our parts, we’re just setting them with strings. But of course, in real life, we’d actually be configuring the parts in these functions, like configuring the Dell Monitor inside the buildMonitor() function.

dellDesktopBuilder.h
#ifndef DELLDESKTOPBUILDER_H
#define DELLDESKTOPBUILDER_H

#include "desktopBuilder.h"

class DellDesktopBuilder : public DesktopBuilder {
	void buildMonitor();
	void buildKeyBoard();
	void buildMouse();
	void buildSpeaker();
	void buildRam();
	void buildProcessor();
	void buildMotherBoard();
};

#endif // !DELLDESKTOPBUILDER_H
dellDeskTopBuilder.cpp
#include "dellDesktopBuilder.h"

void DellDesktopBuilder::buildMonitor() {
	desktop->setMonitor("Dell Monitor");
}
void DellDesktopBuilder::buildKeyBoard() {
	desktop->setKeyBoard("Dell KeyBoard");
}
void DellDesktopBuilder::buildMouse() {
	desktop->setMouse("Dell Mouse");
}
void DellDesktopBuilder::buildSpeaker() {
	desktop->setSpeaker("Dell Speaker");
}
void DellDesktopBuilder::buildRam() {
	desktop->setRam("Dell Ram");
}
void DellDesktopBuilder::buildProcessor() {
	desktop->setProcessor("Dell Processor");
}
void DellDesktopBuilder::buildMotherBoard() {
	desktop->setMotherBoard("Dell Mother Board");
}
hpDesktopBuilder.h
#ifndef HPDESKTOPBUILDER_H
#define HPDESKTOPBUILDER_H

#include "desktopBuilder.h"

class HpDesktopBuilder : public DesktopBuilder {
	void buildMonitor();
	void buildKeyBoard();
	void buildMouse();
	void buildSpeaker();
	void buildRam();
	void buildProcessor();
	void buildMotherBoard();
};

#endif // !HPDESKTOPBUILDER_H
hpDesktopBuilder.cpp
#include "hpDesktopBuilder.h"

void HpDesktopBuilder::buildMonitor() {
	desktop->setMonitor("HP Monitor");
}
void HpDesktopBuilder::buildKeyBoard() {
	desktop->setKeyBoard("HP KeyBoard");
}
void HpDesktopBuilder::buildMouse() {
	desktop->setMouse("HP Mouse");
}
void HpDesktopBuilder::buildSpeaker() {
	desktop->setSpeaker("HP Speaker");
}
void HpDesktopBuilder::buildRam() {
	desktop->setRam("HP Ram");
}
void HpDesktopBuilder::buildProcessor() {
	desktop->setProcessor("HP Processor");
}
void HpDesktopBuilder::buildMotherBoard() {
	desktop->setMotherBoard("HP Mother Board");
}
  1. Creating the director

Alright, our final component! The builder will be sent to the director and the director will use the builder to create the product. Let’s make sure to include the desktopBuilder header here.

To summarize what’s happening here – the builder has the desktop product, the director will now have the builder, and will use the builder to create the product. Simple, right? Let’s do it.

In our class, DesktopDirector, we’ll add the DesktopBuilder in the private section. And in our constructor, we simply pass a desktop builder which will be assigned to the variable we have just added.

What is the main function of the director?

It describes the process by which our product will be built. So now let’s actually build a product in the desktop director, using a BuildDesktop() function which is going to return the desktop.

Since our director already has the desktop builder, and this builder has all the build functions, we’re simply going to call those build functions one by one to actually build the desktop product.

So we might start with buildKeyboard(), and then buildMonitor(), and similarly we build all the parts. This function is designed to return a desktop pointer, so we’re going to use the getDesktop() function in the builder to return the desktop.

Something to note here! We’re calling the builder’s getter here to return the desktop pointer. Some people might just use this function to build the product and not return it, and instead return the product through a separate getter. We’ll add that getter function here as well to showcase how it’s done.

#ifndef DESKTOPDIRECTOR
#define DESKTOPDIRECTOR

#include "desktopBuilder.h"

class DesktopDirector {
private:
	DesktopBuilder* desktopBuilder;
public:
	DesktopDirector(DesktopBuilder* pDesktopBuilder) {
		desktopBuilder = pDesktopBuilder;
	}
	Desktop* getDesktop() {
		return desktopBuilder->getDesktop();
	}
	Desktop* BuildDesktop() {
		desktopBuilder->buildMonitor();
		desktopBuilder->buildMouse();
		desktopBuilder->buildRam();
		desktopBuilder->buildProcessor();
		desktopBuilder->buildKeyBoard();
		desktopBuilder->buildSpeaker();
		desktopBuilder->buildMotherBoard();
		return desktopBuilder->getDesktop();
	}
};

#endif

What’s happening here is that the director is simply crafting a process that utilises the functions within the builder itself to build the product. Going back to our construction example, the civil engineer will just describe the processes to be followed in a specific order whereas it’s the builders themselves who build the house.

In our director, we pass a desktopBuilder which could be a Dell builder or an HP builder – it doesn’t really matter. What we do is simply hand off the builder to the director, ask the director to build the configurations, and return the desktop product.

And finally, now that we’ve built all four components, let’s see how our client can use this Builder design pattern and how easy it is for them to use it.

In our main() function, let’s create a couple of different builders (HpDesktopBuilder & DellDesktopBuilder) and pass them to their own individual directors. And since our directors actually return the desktops, let’s use the buildDesktop() function to fetch 2 desktops. If you recall, we’d created a showSpecs() function in the desktop class, and we can use that to see how the desktops are built.

#include "hpDesktopBuilder.h"
#include "dellDesktopBuilder.h"
#include "desktopDirector.h"

int main() {
	HpDesktopBuilder* hpDesktopBuilder = new HpDesktopBuilder();
	DellDesktopBuilder* dellDesktopBuilder = new DellDesktopBuilder();

	DesktopDirector* director1 = new DesktopDirector(hpDesktopBuilder);
	DesktopDirector* director2 = new DesktopDirector(dellDesktopBuilder);

	Desktop* desktop1 = director1->BuildDesktop();
	Desktop* desktop2 = director2->BuildDesktop();

	desktop1->showSpecs();
	desktop2->showSpecs();

	return 0;
}


//BUILDER+ FACTORY

Conclusion

As you can see, we’ve made the construction of a very complex object into a very easy process. Note here that some people might not create a separate director component and might just place all that code in the client function, which is perfectly fine.

This pattern is very commonly used since we have to often build highly complex objects in real-life applications. Builder design pattern significantly increases the number of lines of code you need to write but it’s worth the time and effort since it also significantly increases the modularity and the readability of our code.

Would you like to watch a video walking you through this Builder design pattern implementation? Check out our video on the official YouTube channel!

Was This Helpful?

Scan to Pay With Your Favourite Upi App
to Buy me a Coffee!

keerti upi qr code