Builder Design Pattern: When to use?

In one of my previous blog posts, I have explained how, many a time, it is advantageous to use “Static Factory Methods” to create objects, instead of using “public constructors” (you can check out the blog post here).

Introduction

All is good until the number of parameters, required to create an object, starts to increase and there are optional parameters present. Both “Static Factory Methods” and “public constructors” can’t scale gracefully and simplify object creation with an increase in the number of optional or identical parameters.

Discussion

Let us consider a scenario of creating a “House” object where “name” and “numberOfRooms” are mandatory parameters for object construction and “color”, “city” are the optional ones.

package com.builderpattern;

public class House {

    private final String name;
    private final int numberOfRooms;
    private final String color;
    private final String city;

}

If a client/user has to create an object with all the mandatory parameters (“name”, “numberOfRooms”) and with only the “color” optional parameter, then to enable that, one of the common patterns that most programmers generally end up writing is the “Telescoping Constructor Pattern” where they would create a constructor with the mandatory parameters, another constructor with one optional + mandatory parameters, another one with two optional + mandatory parameters, another one with all the optional + mandatory parameters and so on. Like below:

package com.builderpattern;

public class House {

    private final String name;
    private final int numberOfRooms;
    private final String color;
    private final String city;


    public House(String name, int numberOfRooms) {
        this(name, numberOfRooms, "");
    }

    public House(String name, int numberOfRooms, String color) {
        this(name, numberOfRooms, color, "");
    }

    public House(String name, int numberOfRooms, String color, String city) {
        this.name = name;
        this.numberOfRooms = numberOfRooms;
        this.color = color;
        this.city = city;
    }

    @Override
    public String toString() {
        return "House{" +
                "name='" + name + '\'' +
                ", numberOfRooms=" + numberOfRooms +
                ", color='" + color + '\'' +
                ", city='" + city + '\'' +
                '}';
    }
}

The first problem with adopting this approach is that – the client/user may need to send all the optional parameters obligatorily to maintain the defined order of the optional parameters for object construction. Even if it does not want to do that. Think about the situation where the client wants to create an object with “name”, “numberOfRooms” and “city”, but not with any “color”. Unwillingly it has to send some empty value for the “color” string type parameter too to avoid a compilation error. 

House house = new House("Jasmine", 3, "", “Bengaluru");

And for this, I am giving an example with only 2 optional parameters. What if the number of optional parameters is many? The client needs to send an empty/dummy value for the optional parameters that it does not need. The code will look ugly and that will certainly not be a good approach to follow.

The second problem in this telescoping pattern is that the client needs to understand what all those passed values mean and always keep a count of the number of parameters. With a large number of parameters, this will give nightmares to whoever is using or reading it. There is a high chance of having bugs in these places. The compiler may not throw an error on using a parameter value in place of an identical parameter, but there can be issues during the runtime and subtle hard-to-find bugs can be present.

Another way of approaching this problem of creating objects with optional parameters is by using the “Java Beans” style. In this approach, the programmer would create an empty non-parameterised constructor at the start and then use setter-like methods that will help to set respective parameters. The appropriate setter-like methods will be called by the client/user to set its required parameters and also its mandatory parameters. Consider the same example of creating a “House” object:

package com.builderpattern;

public class House {

    private String name;
    private int numberOfRooms;
    private String color;
    private String city;

    public House() {
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setNumberOfRooms(int numberOfRooms) {
        this.numberOfRooms = numberOfRooms;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void setCity(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "House{" +
                "name='" + name + '\'' +
                ", numberOfRooms=" + numberOfRooms +
                ", color='" + color + '\'' +
                ", city='" + city + '\'' +
                '}';
    }
}

When the code is run from the “main” method:

package com.builderpattern;

public class Main {

    public static void main(String[] args) {
        House house = new House();
        house.setName("Residence");
        house.setNumberOfRooms(3);
        house.setColor("White");
        house.setCity("Pune");
        System.out.println(house);
    }
}

Output:

House{name='Residence', numberOfRooms=3, color='White', city='Pune'}

This way of creating an object does seem to have removed the disadvantages of the telescoping pattern and is easy to read. But the issue with using this approach is that it will always prevent the object to become an immutable object. Hence, it may behave irrationally during multithreading. Also, there will be a high chance of the object state being inconsistent if the object is not “frozen” (by using exceptions before object creation is done) after all the parameters are added. This is because multiple setter calls are being made during the object creation process. The possibility of bugs will increase if an attempt is made to use the object in this inconsistent state.

The third approach that we can take to overcome the problem and which has the advantages of both the “telescoping constructor pattern” and the “java beans” pattern is the “Builder Design Pattern”. In this safe and reliable style, the client/user calls the constructor of a static member “Builder” class with the mandatory parameters and receives a “builder” object in return. Then it calls setter-like methods of the builder object to set the values for the optional parameters. Once every optional parameter is set, it calls a parameterless “build” method of the builder object which returns the newly created immutable object to the client/user. Look at the example below for the creation of the House object:

package builderPattern;

public class House {
    private final String name;
    private final int numberOfRooms;
    private final String color;
    private final String city;

    private House(Builder builder) {
        this.name = builder.name;
        this.numberOfRooms = builder.numberOfRooms;
        this.color = builder.color;
        this.city = builder.city;
    }

    public static class Builder {
        private final String name;
        private final int numberOfRooms;
        private String color = "";
        private String city = "";

        public Builder(String name, int numberOfRooms) {
            this.name = name;
            this.numberOfRooms = numberOfRooms;
        }

        public Builder color(String color) {
            this.color = color;
            return this;
        }

        public Builder city(String city) {
            this.city = city;
            return this;
        }

        public House build() {
            return new House(this);
        }

    }

    @Override
    public String toString() {
        return "House{" +
                "name='" + name + '\'' +
                ", numberOfRooms=" + numberOfRooms +
                ", color='" + color + '\'' +
                ", city='" + city + '\'' +
                '}';
    }
}

When the code is run from the “main” method:

package builderPattern;

public class Main {

    public static void main(String[] args) {
        House house = new House.Builder("Residence", 3).color("White").city("Pune").build();
        System.out.println(house);
    }
}

Output:

House{name='Residence', numberOfRooms=3, color='White', city=‘Pune'}

Here the “Builder” class is a static member of the class it builds (i.e. House). The Builder class contains the required and optional parameters as fields. The required parameters are passed to its constructor and the optional parameters are set by calling the respective setter-like methods as per client/user requirements. These setter-like methods return the Builder object itself so that their invocations can be chained. This chaining results in a Fluent API. When the construction is complete, the “build” method is called. This “build” method will call and return the parent class’s constructor by passing the constructed builder object as a parameter.  By following this approach, the “house” object has become immutable. We can also include validity checks of both the mandatory and the optional parameters by including those checks inside the constructor and setter-like methods respectively of the Builder class. If the checks fail, we can throw the related exceptions.

Conclusion

This Builder design pattern provides great flexibility when doing things or making changes to define how the optional/identical parameters will behave. It also provides the ability to use the same builder to create objects multiple times repeatedly. Some minor and mostly ignorable disadvantages of this pattern are that it sometimes looks quite verbose and since the builder object is called multiple times, it may impact performance in certain situations. But for most cases, if the number of parameters is more it is the best approach to create objects.

Thanks for reading this blog. Hope you have found it useful 😊

If you want to read more on some interesting Java topics, checkout the below:

https://www.sumondey.com/alternative-approaches-to-using-if-else-and-switch-in-code/

https://www.sumondey.com/serialization-and-deserialization-using-jackson-objectmapper/

https://www.sumondey.com/what-your-automation-code-will-look-like-with-the-latest-java15-features/

https://www.sumondey.com/25-cool-java-things-you-may-or-may-not-have-come-across/

https://www.sumondey.com/fillo/

https://www.sumondey.com/lombok-annotations/


Follow me on Twitter and LinkedIn to know more about the topics I love to talk about.