DEV Community

Khaliph
Khaliph

Posted on

Design Patterns for Javascript — Builder Pattern

The builder pattern is a creational design pattern that applies to situations when we have to design or construct objects that have multiple creational steps with lots of complexity.
The builder pattern allows us have a base class that we can always refer back to and pick out methods that are always available from the base class, orchestrate their calls and generally come up with a more direct, simple way of constructing the target class.

The builder class will allow you define steps to create dintinct entities, object instances, or can also allow you orchestrate the creational procedure dynamically.

A QUICK EXAMPLE

Assuming we have a program that creates tea for the team. Consider the code snippet below

class Tea {
    constructor(chocolate, milk, sugar, honey, temperature) {
        return chocolate + milk + sugar + honey + temperature;
    }
}
Enter fullscreen mode Exit fullscreen mode

This snip of code builds a cup of tea for the consumer. For simplicity, let us assume a very straightforward process. Jam all ingredients together and be on your way.

On the surface, this seems very straightforward. Then comes the time this program will be used, probably by a third party or even we ourselves a couple of months down the line and we start to encounter finer issues regarding detail like is the temperature rounded to 2 or 3 decimals? or which comes first…honey or sugar? While it might be easy for us now to just go back and view the definition of the class constructor, we may not have this luxury all the time. This is an instance where we can use a builder.

In a way, think of a builder in this manner;

BUILDERS CAN BE USED TO ABSTRACT FINE OBJECT CONSTRUCTION IMPLEMENTATION DETAILS AND PRESENT A GENERIC INTERFACE TO THE USER

If we write this class as a Builder class, we will be able to abstract some implementation details such as number of decimals, data types, constructor argument order, e.t.c into a friendlier interface.

As it stands, to construct an instance of the Tea class, we need to do the following code:

let MyTea = new Tea(23, null, 5, 3, 23.45);

Using the builder pattern however, we can refactor the Tea class in the following way;

class Tea {
    constructor(chocolate) { // constructor now takes an argument. We could implement a zero-parameter constructor if we desire.
        this._chocolate = chocolate;
        this._milk = null;
        this._sugar = null;
        this._honey = null;
        this._temperature = null;
}
    addMilk (quantity) {
// we can apply transformations to the value here, much like using a setter
        this._milk = quantity;
        return this; // this is the line that does all the magic. I will explain further in a bit
    }
    addSugar (quantity) {
        this._sugar = quantity;
        return this;
    }
    addHoney (quantity) {
        this._honey = quantity;
        return this;
    }
    setTemperature (value) {
        let temperature = Number.parseFloat(value); // like I said, we can control how the passed values are injected into the application using this
        this._temperature = temperature;
        return this;
}
    brewCup () {
        return this._chocolate + this._milk + this._honey + this._sugar + this._temperature;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, to make a cup of tea, we could go about it this way.

let MyTea = new Tea(‘Choco’);

myTea.addMilk(‘milk’).addHoney(‘honey’).addSugar(‘sugar’).setTemperature(23.918).brewCup();
Enter fullscreen mode Exit fullscreen mode

Notice how the order in which the methods are called do not really matter towards the end product? This is because the builder pattern continually returns the class instance of the builder, and this class instance will always expose all its methods to be available for the consumer to call at any point.
You could literally do a .addMilk().addMilk().addMilk() and it would fly, because the this being returned by the methods will always carry the methods along with.

Another way to execute the builder pattern lies in the use of abstract classes and concrete classes. JavaScript however has no concept of abstract or concrete entities, so we have limited constructs to work with if we do not mock the abstract functionality. However, the idea is you have a CREATOR, a STENCIL/TEMPLATE class called the **ABSTRACT **class, and you generate an instance of a **TARGET **class or object.

Passing an abstract class through a factory/builder

Why might this be a better approach? At some point, the TARGET classes may also get large enough for them to be abstracted into separate classes of their own. To illustrate this approach to the builder pattern, we can use the case of an Automobile Manufacturing company’s production line as an example.

Assuming I open up a company to cater to the many modes of transportation in China. Here we are, looking to produce bicycles, cars and ships at very large quantities. We need to set production lines up. This are the lines that will churn out product.

Each line should cater to a specific mode of transportation. So, we setup 3 production lines in total.

Assuming we have a factory;

class VehicleFactory {
    constructor(builder) {
        this._builder = builder
    }
    build () {
        this._builder.step1();
        this._builder.step2();
        this._builder.step3();
        return this._builder.getBuild();
    }
}
Enter fullscreen mode Exit fullscreen mode

This is a simple factory. Not too many details here, although factories could get more complex. However, for the purpose of this tutorial, let us say our factory is this simple.

We can see that this factory exposes a build method, that then interacts with the builder we initialized our class with, and spits out a product. We can also see a caveat here, all our Abstract classes must expose methods named step1, step2, step3 and getBuild. We can however enjoy the abstraction we get when we can create individual abstract classes, we enjoy better control on the classes as they are smaller, easier to understand and easier to think about.

class BicycleFactory {
    constructor(product) {
        this._product = product;
    }
    step1 () {
        return 'Add 2 tyres'
    }
    step2 () {
        return 'Add handlebar controls'
    }
    step3 () {
        return 'Add manual power'
    }
    getBuild () {
        return 'Build'
    }
}
class CarFactory {
    constructor(product) {
        this._product = product;
    }
    step1 () {
        return 'Add 4 tyres'
    }
    step2 () {
        return 'Add steering controls'
    }
    step3 () {
        return 'Add petrol power'
    }
    getBuild () {
        return 'Build'
    }
}
class ShipFactory {
    constructor(product) {
        this._product = product;
    }
    step1 () {
        return 'Add floatation technology'
    }
    step2 () {
        return 'Add rudder controls'
    }
    step3 () {
        return 'Add diesel power'
    }
    getBuild () {
        return 'Build'
    }
}
Enter fullscreen mode Exit fullscreen mode

We can see all three factories expose the same interface. This makes it possible for our abstract factory to adapt to our factory and create concrete products. We can now say

let AbstractCar = new CarFactory(‘car’);
let AbstractBicycle = new BicycleFactory(‘bicycle’);
let AbstractShip = new ShipFactory(‘ship’);
let CarShop = new VehicleFactory(AbstractCar);
let BicycleShop = new VehicleFactory(AbstractBicycle);
let ShipShop = new VehicleFactory(AbstractShip);
Enter fullscreen mode Exit fullscreen mode

We can then get our concrete classes by calling:

CarShop.build();
BicycleShop.build();
ShipShop.build();
Enter fullscreen mode Exit fullscreen mode

Now, we have once again abstracted the creation of the concrete classes away from the factory. We have successfully separated the process of creation (the actual factory) from how the product is created (the abstract factory);

This is what the builder pattern is about and how it can be successfully implemented with Javascript.

FURTHER READING

  1. Understand Inheritance In-Depth — OpenGenus

  2. The Difference Between Abstract Classes and Concrete Classes — Geeksforgeeks

Top comments (0)