TL; DR
Source code can be found on GitHub, jsBuilder repo
Design Patterns - The Builder
The Builder pattern is a creational design pattern, which should be applied when complex objects have to be created. When applying this pattern the goal is to separate the object creation (or initialization) logic from the representation.
This pattern should be used when object creation is complicated and involves a lot of code.
UML diagram1 with the Builder pattern:
This pattern has three components, the Director which uses the Builder to create objects, the role of the Director is to coordinate the object creation in case there are multiple Builders involved. The Director also provides an extra abstraction layer when implementing this pattern.
The Builder is an abstraction layer above all the Concrete Builders which create the objects. In JavaScript the Builder is not separated clearly and in some case it can be left out completely (although I forced it to be in the sample code).
The Concrete Builders implement the real object creation. Concrete Builders, internally, can use Factories for building parts of the final object (I used factories in my implementation to demonstrate this).
I present the Builder pattern using an analogy.
Lets imagine the process of "building" a cake. In this analogy a cake is considered a complex object, because it is hard to create. The cake object has three parts, the layers, the cream and the topping.
I modeled this scenario in code. Let's start from the Director implementation. In this scenario the Director or Coordinator is represented by the PastryCook
function, this handles everything related to "building" the cake.
function PastryCook() {
var chocolateCakeBuilder = ChocolateCakeBuilder.getBuilder();
var strawberryCakeBuilder = StrawberryCakeBuilder.getBuilder();
var traditionalCakeBuilder = TraditionalCakeBuilder.getBuilder();
return {
buildCake: function(flavor) {
var cake = null;
switch (flavor) {
case 'chocolate':
cake = chocolateCakeBuilder.buildCake();
break;
case 'strawberry':
cake = strawberryCakeBuilder.buildCake();
break;
default:
cake = traditionalCakeBuilder.buildCake();
break;
}
return cake;
}
};
}
The PastryCook
has a buildCake
function, which receives the flavor
argument. Based on the value of the argument I used different Builders to build the cake. By adding the possibility to create different cake objects based on a parameter passed to the Director I could demonstrate how to use multiple builders.
The implementation of ChocolateCakeBuilder
handles the steps of building a chocolate cake. In this Builder I used factories to simplify the implementation and to provide an extra layer of abstraction.
function ChocolateCakeBuilder() {
var layerFactory = LayerFactory.getInstance();
var creamFactory = CreamFactory.getInstance();
var toppingFactory = ToppingFactory.getInstance();
return {
buildCake: function() {
return {
layer: layerFactory.getStandard(),
cream: creamFactory.getChocolate(),
topping: toppingFactory.getChocolate()
}
}
};
}
Every Builder implements the buildCake
function, this is the method which delivers the Product (referring to the UML diagram).
The StrawberryCakeBuilder
is very similar in implementation:
function StrawberryCakeBuilder() {
var layerFactory = LayerFactory.getInstance();
var creamFactory = CreamFactory.getInstance();
var toppingFactory = ToppingFactory.getInstance();
return {
buildCake: function() {
return {
layer: layerFactory.getLowCarb(),
cream: creamFactory.getWhipped(),
topping: toppingFactory.getStrawberry()
}
}
};
}
For the Builders I used Factories, because these (in general) add more flexibility to the code and reduce the maintenance cost of the codebase.
The source code of ToppingFactory
is below; all it does is return a string
with the selected topping.
function ToppingFactory() {
return {
getChocolate: function() {
return 'Topping: [Chocolate]';
},
getVanilla: function() {
return 'Topping: [Vanilla]';
},
getStrawberry: function() {
return 'Topping: [Strawberry]';
},
};
}
module.exports = {
getInstance: ToppingFactory
};
Sample code was written for node
, the index.js
has the demo code:
var PastryCook = require('./PastryCook');
var cakeBuilder = PastryCook.getBuilder();
var chocolateCake = cakeBuilder.buildCake('chocolate');
var strawberryCake = cakeBuilder.buildCake('strawberry');
var cake = cakeBuilder.buildCake();
console.log("The Chocolate cake is compound of:");
console.log(JSON.stringify(chocolateCake, null, 2));
console.log("The Strawberry cake is compound of:");
console.log(JSON.stringify(strawberryCake, null, 2));
console.log("The cake is compound of:");
console.log(JSON.stringify(cake, null, 2));
Once the code is executed the output should be:
The Chocolate cake is compound of:
{
"layer": "Layer: [Standard]",
"cream": "Cream: [Chocolate]",
"topping": "Topping: [Chocolate]"
}
The Strawberry cake is compound of:
{
"layer": "Layer: [LowCarb]",
"cream": "Cream: [Whipped]",
"topping": "Topping: [Strawberry]"
}
The cake is compound of:
{
"layer": "Layer: [Standard]",
"cream": "Cream: [Peanut Butter]",
"topping": "Topping: [Strawberry]"
}
I tried to keep the implementation clean and developer friendly so the real use case of the Builder design pattern can be seen and understood.
If you enjoyed reading this blog post, please share it or like the Facebook page of the website to get regular updates.
Thank you for reading!
Image Source is Wikipedia ↩