Builder pattern

There are so many different design patterns in programming, e.g., builder pattern, guided builder pattern, factory/supplier pattern, factory-method pattern, visitor pattern, etc. Design patterns are merely guidelines for effective coding, but they have their merits in solving commonly re-occuring problems. This article explains one of design patterns called builder pattern. Before this post becomes TL;DR, let’s dive directly to codes in Java.

Let’s start with a problem statement. Suppose you have a typical Employee class with two attributes: id and name as follows.

public class Employee {
    private final int id;    //required
    private final String name;  //required
    
    public Employee(final int id, final String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

You now have business requirement to add more optional fields to the class: address, phoneNumber,  dateOfBirth, placeOfBirth, ssn. You will have many constructors shown below.

import java.util.Date;

public class Employee {
    private final int id;               //required
    private final String name;          //required
    private final String address;       //optional
    private final String phoneNumber;   //optional
    private final Date dateOfBirth;     //optional
    private final String placeOfBirth;  //optional
    private final String ssn;           //optional

    public Employee(final int id, final String name) {
        this(id, name, "", "", null, "", "");
    }

    public Employee(final int id, final String name, String address) {
        this(id, name, address, "", null, "", "");
    }

    public Employee(final int id, final String name,
                    final String address, final String phoneNumber) {
        this(id, name, address, phoneNumber, null, "", "");
    }

    public Employee(final int id, final String name,
                    final String address, final String phoneNumber,
                    final Date dateOfBirth) {
        this(id, name, address, phoneNumber, dateOfBirth, "", "");
    }

    public Employee(final int id, final String name,
                    final String address, final String phoneNumber,
                    final Date dateOfBirth, final String placeOfBirth) {
        this(id, name, address, phoneNumber, dateOfBirth, placeOfBirth, "");
    }

    public Employee(final int id, final String name, final String address,
                    final String phoneNumber, final Date dateOfBirth,
                    final String placeOfBirth, final String ssn) {
        this.id = id;
        this.name = name;
        this.address = address;
        this.phoneNumber = phoneNumber;
        this.dateOfBirth = dateOfBirth;
        this.placeOfBirth = placeOfBirth;
        this.ssn = ssn;
    }
    
    ...   
}

You can see the downside immediately. First, you cannot make another constructor which have the same signature. For instance, constructor with id, name, and phoneNumber is not possible to be created since you already have a constructor with id, name, address (i.e., int, String, String signature). Second, as the field number grows, you will have difficulty in maintaining the codes and deciding which constructors are needed. Third, it’s not clear to the users of this class which constructor they should call.

We can easily solve these problems by simply following JavaBeans convention, i.e, setting all fields to be non-final and providing setter methods for all fields. However, this introduces problem of mutable state (a big no for concurrency programming) which hard to debug. This also means any instantiated object of the class can be in inconsistent state throughout its lifetime within the client/user codes.

Fortunately, we have builder pattern comes to the rescue. Builder pattern tackles all those problems mentioned above without losing the advantages of immutable state.

import java.util.Date;

public class Employee {
    private final int id;               //required
    private final String name;          //required
    private final String address;       //optional
    private final String phoneNumber;   //optional
    private final Date dateOfBirth;     //optional
    private final String placeOfBirth;  //optional
    private final String ssn;           //optional

    private Employee(Builder builder) {
        this.id = builder.id;
        this.name = builder.name;
        this.address = builder.address;
        this.phoneNumber = builder.phoneNumber;
        this.dateOfBirth = builder.dateOfBirth;
        this.placeOfBirth = builder.placeOfBirth;
        this.ssn = builder.ssn;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getAddress() {
        return address;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public Date getDateOfBirth() {
        return dateOfBirth;
    }

    public String getPlaceOfBirth() {
        return placeOfBirth;
    }

    public String getSsn() {
        return ssn;
    }

    public static Builder builder(int id, String name) {
        return new Builder(id, name);
    }

    public static class Builder {
        private final int id;
        private final String name;
        private String address;
        private String phoneNumber;
        private Date dateOfBirth;
        private String placeOfBirth;
        private String ssn;

        private Builder(int id, String name) {
            this.id = id;
            this.name = name;
        }

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

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

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

        public Builder dateOfBirth(Date dateOfBirth) {
            this.dateOfBirth = dateOfBirth;
            return this;
        }

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

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

The constructor for Employee is private, so new object can only be instantiated by using the builder static method which return Builder. Also, the Builder class has a fluent interface to set the value of optional fields, whereas the required fields are passed as constructor argument. You can look on how readable the code for instantiating a new Employee object.

Employee employee = Employee.builder(1, "Watson")
        .address("Baker street, London")
        .phoneNumber("+1234567890")
        .build();

you can provide an extra static unbuild method inside the Employee class. This is useful if you want to have a new Builder with the same fields value of Employee object and then assign different values to certain fields.

import java.util.Date;

public class Employee {
    private final int id;               //required
    private final String name;          //required
    private final String address;       //optional
    private final String phoneNumber;   //optional
    private final Date dateOfBirth;     //optional
    private final String placeOfBirth;  //optional
    private final String ssn;           //optional

    private Employee(Builder builder) {
        this.id = builder.id;
        this.name = builder.name;
        this.address = builder.address;
        this.phoneNumber = builder.phoneNumber;
        this.dateOfBirth = builder.dateOfBirth;
        this.placeOfBirth = builder.placeOfBirth;
        this.ssn = builder.ssn;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getAddress() {
        return address;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public Date getDateOfBirth() {
        return dateOfBirth;
    }

    public String getPlaceOfBirth() {
        return placeOfBirth;
    }

    public String getSsn() {
        return ssn;
    }

    public Builder unbuild() {
        return new Builder(this);
    }

    public static class Builder {
        private final int id;
        private final String name;
        private String address;
        private String phoneNumber;
        private Date dateOfBirth;
        private String placeOfBirth;
        private String ssn;

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

        private Builder(Employee employee) {
            this.id = employee.getId();
            this.name = employee.getName();
            this.address = employee.getAddress();
            this.phoneNumber = employee.getPhoneNumber();
            this.dateOfBirth = employee.getDateOfBirth();
            this.placeOfBirth = employee.getPlaceOfBirth();
            this.ssn = employee.getSsn();
        }

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

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

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

        public Builder dateOfBirth(Date dateOfBirth) {
            this.dateOfBirth = dateOfBirth;
            return this;
        }

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

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

The example of unbuild method usage is as follows.

Employee employee = Employee.builder(1, "Watson")
        .address("Baker street, London")
        .phoneNumber("+1234567890")
        .build();

Employee employee2 = employee.unbuild()
        .ssn("123455555")
        .build();

I hope this post gives you a good overview of builder pattern. Next time I’ll discuss other design patterns.

One thought on “Builder pattern

Leave a comment