Builder is one of the Creational Design Patterns, which help you deal with how objects are created. You should pay close attention to your object creation techniques that may morph into serious design problems if you neglect them.
Start here if you're just getting started with Design Patterns in JavaScript
Real-world examples
Picture having to assemble tiny lego pieces into what looks like a house. There are several different ways of grouping them into various representations.
A basic house will have a door, zero or more windows, and a roof with surrounding walls. Some may even have a garage, and some have a swimming pool. We'd wish for the process to be simpler and flexible to accommodate changes.
A step-by-step assembly of the lego pieces at your disposal forms the essence of the Builder pattern.
Objects are thus constructed following a sequence of steps one after the other instead of arriving at it all at once.
JavaScript objects are a collection of properties and methods. Properties are fields or variables associated with the object. And methods are functions that you're going to invoke to manipulate the fields. Objects' properties are closely related to each other, at least semantically in the worst case.
JavaScript provides many ways to create objects.
Object Initializer
The most common method of creating an object in JavaScript is an object literal
notation.
const employee = {
firstName: 'Sam',
lastName: 'Greene',
id: '12340987',
phone: '07123456789'
};
where firstName
, lastName
, id
and phone
are properties of the object named employee
.
Constructor
Alternatively, we can use a constructor to create multiple instances of similar objects using the new
keyword.
class Employee {
constructor(fname, lname, id, ph) {
this.firstName = fname;
this.lastName = lname;
this.id = id;
this.ph;
}
};
const employee1 = new Employee('Sam', 'Greene', 12340987, '07123456789');
const employee2 = new Employee('Nate', 'Tyson', 56478390, '07987654321');
Using Object.create
Object.create()
is an inbuilt function that you can use to create multiple instances of an object. It allows you to pass in the template object as an input, thus choosing the prototype for the objects as desired, without a constructor.
const Employee = {
isAdmin: false,
getRole: function() {
return this.isAdmin ? 'Admin' : 'RegularEmp';
};
};
const emp1 = Object.create(Employee);
emp1.getRole(); //'RegularEmp'
const emp2 = Object.create(Employee);
emp2.isAdmin = true;
emp2.getRole(); //'Admin'
In this example, both emp1
and emp2
inherited the method getRole
. Once you've set the isAdmin
to true
, the role of emp2
changed to Admin
.
The problem
So it looks like there's already a bunch of ways to create objects in JavaScript. Why would we want to invent something different?
Let's consider two important questions:
- What if you need to create a complex object comprising too many fields and properties?
- What if you need to create multiple instances of almost the same object?
Object literal is not a good choice for it doesn't help with code reusability. Every time you need a new object, you'd have to list all its fields over and over.
Constructor would solve the issue to a certain extent, but it'd be cumbersome. You'd have to remember the inputs to it, some of which are mandatory and others aren't.
The Builder Pattern
What if you had a way to address just the above two problems while hiding the internal representation from anyone using it?
Objects are all around us in real life. And you can compare JavaScript objects with them.
A car, for example, comes in different colors or with a different number of seats. A house can have a various number of doors, windows, and chimneys. Differences are everywhere, yet there are some similarities.
The Builder Pattern gives you a way to simplify creating a complex object by separating it from its representation.
This pattern eases the construction of an object, taking the process through a step-by-step progression while also encapsulating (hiding) the implementation details of these steps.
Let's look into what a flexible design looks like for a simple OTG builder and how it evolves with the help of the Builder pattern.
Building an OTG using the Builder Design Pattern
There should be provision to build OTGs in various colors, models, pricing, and features. Some are built to support high temperature and a good timer selection span. For example, lower-end models come with low-temperature settings and a default timer.
Step 1: Class OTG
A simple class for OTG here has a JS constructor that takes in four parameters. Every OTG instance has a default title, an overridable temperature, and time selections which are by default set to 150 and 30 respectively.
class OTG {
constructor(model, color, maxTemperature, maxTimeSelection) {
this.model = model;
this.title = 'OTG';
this.color = color;
this.maxTemperature = maxTemperature || 150;
this.maxTimeSelection = maxTimeSelection || 30;
}
}
const redOTG = new OTG('LG', 'red');
const highTempOTG = new OTG('LG', 'black', 200);
const highendTimeOTG = new OTG('LG', 'red', '150', '60');
The above code looks ok for now. But there's a problem with it.
Imagine having to create multiple selections of OTG instances. It will get difficult to keep track of the parameters and their order in the constructor. Also, at times, there will be no need for sending in certain optional parameters.
Step 2: Create a builder class
Let's look at how we can avoid cramming the constructor to accept minimal parameters.
class OTGBuilder {
constructor(model, color) {
this.model = model;
this.title = 'OTG';
this.color = color;
}
}
OTGBuilder class here is responsible for the creation of instances of the OTG class. But it doesn't do this yet. For now, it just captures the basic fields model and color.
These fields are a must, so we include them directly in the constructor.
Step 3: Add optional parameters as separate functions
You've probably noticed that the fields maxTemperature
and maxTimeSelection
can't be configured with a the constructor.
These properties are optional and we'll create separate functions to set them.
class OTGBuilder {
constructor(model, color) {
this.model = model;
this.title = 'OTG';
this.color = color;
}
setMaxTemperature(temp) {
this.maxTemperature = temp;
return this;
}
setMaxTimeSelection(maxTime) {
this.maxTimeSelection = maxTime;
return this;
}
}
It's necessary to add return this;
to make sure we can use chained call when we're working with the Builder Design Pattern.
const otg = new OTGBuilder('MorphyRichards', 'Black')
.setMaxTemperature(250)
.setMaxTimeSelection(60);
Step 4: Build
The only remaining problem is that the object otg
isn't the proper OTG yet. It's an instance of the OTGBuilder
class. And you need to implement the last step of the process, which is the build()
method.
class OTGBuilder {
constructor(model, color) {
this.model = model;
this.title = 'OTG';
this.color = color;
}
setMaxTemperature(temp) {
this.maxTemperature = temp;
return this;
}
setMaxTimeSelection(maxTime) {
this.maxTimeSelection = maxTime;
return this;
}
build() {
return new OTG(this.model, this.color,
this.maxTemperature, this.maxTimeSelection);
}
}
Now, you can call the build()
method when you're ready to create your OTG.
const basicOTG = new OTGBuilder('MorphyRichards', 'Black')
.setMaxTemperature(250)
.setMaxTimeSelection(60)
.build();
If you want to, you can always skip the steps to set optional parameters.
const default = new OTGBuilder('Generic OTG', 'White')
.build();
The properties maxTemperature
and maxTimeSelection
will default to 150 and 30 unless you overwrite them with setMaxTemperature
and setMaxTimeSelection
.
Thanks for reading!
I hope you liked this intro the Builder Design Pattern. Like/share/comment if you want me to cover more JavaScript Design Patterns in the future.
Get my free e-book to prepare for the technical interview or start to Learn Full-Stack JavaScript
Top comments (0)