Builder Design Pattern

This blog covers how to use the builder design pattern and the problems it addresses.

Assume you had a code below:
A user class that has a name, age, phone_number and address, where an address is a class that has street and province.

This is the common way to express this in most languages.

class Address {
    constructor(street, province) {
        this.street = street;
        this.province = province;
    }
}

class User {
    constructor(name, age, phone, address) {
        this.name = name;
        this.age = age;
        this.phone = phone;
        this.address = address;
    }
}


When you want to create a user, you can simple do:

user = new User(‘Emmanuel’);

This will create a user with a name Emmanuel.

There are 2 main problems with this approach:
If you wanted to add an address. To the user:

 user = new User(‘Emmanuel’, undefined, undefined, new Address(‘386’, ‘KK’));

Problem 1
Notice you have to explicitly write undefined twice; for the age and phone and then you can write the address. This brings a problem during reading the code to understand and also debugging.

Problem 2

If another field is to be added to the user class, let’s say an email field.

class User {
    constructor(name, age, phone, email, address) {
        this.name = name;
        this.age = age;
        this.phone = phone;
        this.email = email;
        this.address = address;
    }
}

you have to change almost all the instances where a user is created to cater for this change.
e.g this:

 user = new User(‘Emmanuel’, undefined, undefined, new Address(‘386’, ‘KK’));

will have to change to this:

 user = new User(‘Emmanuel’, undefined, undefined, undefined, new Address(‘386’, ‘KK’));

otherwise the email field will point to the new address passed to it.

Solution 1
The Builder pattern can be used to solve the 2 main problems.

A userBuilder class can be added and this class is used to create the user instead.

class Address {
    constructor(street, province) {
        this.street = street;
        this.province = province;
    }
}

class User {
    constructor(name) {
        this.name = name;
    }
}

class UserBuilder {
    constructor(name) {
        this.user = new User(name);
    }
  
    setAge(age) {
        this.user.age = age;
        return this;
    }

    setPhone(phone) {
        this.user.phone = phone;
        return this;
    }

    setAddress(address) {
        this.user.address = address;
        return this;
    }

    build() {
        return this.user;
    }
}

Notice that we have taken away all the optional params from the User class and left only name.
Then we created a UserBuilder that has setters for all the optional params.
The constructor of the UserBuilder creates a new user and the build method returns the user.

This allows us to create a user like this:

user = new UserBuilder(“Emmanuel”).build();

Notice that we have to use the builder and not the user class. We also have `return this` in all the setters, this simply returns the builder instance allowing us to chain the setters to create a user with multiple attributes:

user = new UserBuilder(“Emmanuel”).setAge(20).setPhone(1234).setAddress(new Address(‘386’, ‘KK’)).build()

Notice that to add an address, we don’t have to use the undefined because the order of the params doesn’t matter:

user = new UserBuilder(“Emmanuel”).setAddress(new Address(‘386’, ‘KK’)).build();

Also to add a new field, we just add a setter for it in the builder.

setEmail(email) {
    this.user.email = email;
    return this;
}

This doesn’t require us to change anything outside the builder class.
The main disadvantage with this pattern is that it requires the creation of an extra class (builder class) and extra code.
There is however a different way you can use this pattern but it is more specific to Javascript.

Solution 2
The address class remains the same and the user class becomes:

class User {
    constructor(name, {age, phone, address} = {}) {
        this.name = name;
        this.age = age;
        this.phone = phone;
        this.address = address;
    }
}

Notice that the optional params are  defined in a JavaScript Object and are initialised to an empty object ({})

This allows us to create a user like this:

user = new User(‘Emmanuel’);

If we wanted to add some of the optional fields like an address, we use JSON:

user = new User(‘Emmanuel’, {address : new Address(’386’, ‘KK’)});

This is even better than the first solution because it addresses all the problems the first solution solves and in addition it addresses the problems the first solution brought about like too much code and an additional class.

Notice that to add an address, we don’t have to use the undefined because the order of the params doesn’t matter.

The disadvantages with this approach :
* We have to keep track of the named and optional parameters.
* We have to always pass in a JSON for the optional parameters.

Previous
Previous

5th April 2020

Next
Next

Learning Outcomes - 03 April