Wednesday, January 13, 2021

Builder Design Pattern

Oracle Java Exam Prep, Oracle Java Tutorial and Material, Core Java, Oracle Java Learning, Oracle Java certification

We will discuss Builder design pattern.

Key topics we are going to discuss are :

– Which category Builder Design Pattern falls in ?

– What problem builder Pattern is solving or when to use Builder pattern?

– Builder Pattern

– Builder Pattern Example

– Advantages of Builder Pattern

– Disadvantages of Builder Pattern

Which category Builder Design Pattern falls in ?

Builder pattern falls under Creational design patterns category, as it deals with the creation of object(s).Please note that Builder design pattern that I am going to describe here is not GOF design pattern but the one suggested by Joshua block in

Effective Java, as I personally see this pattern used for more often or more practical than the one suggested by GOF.

What problem builder Pattern is solving or when to use Builder pattern?

In nutshell, you should use Builder design pattern when :

– You have a class, which has some mandatory fields and some optional fields, which in other words means that your object can be constructed in various ways as per requirements. Although you are free to use it with a class with all mandatory fields as well when number of fields are too many(usually more than four is a good candidate).

– You want objects of your class to be immutable, which means once objects are instantiated there state can not be changed after that.

Now lets discuss these points in more detail.

You have a class with some mandatory and some optional fields :

What is the problem with having optional fields.

Let us say you have below Student class with mandatory and optional fields and have a constructor with all the fields.

package com.blogspot.oraclejavacertified;

public class Student {

    //mandatory fields

    private final String id;

    private String firstName;

    private String lastName;                                                                                                                                            //optional fields

    private String age;

    private String houseNumber;

    private String streetNumber;

    public Student(String id, String firstName, String lastName, String age, String houseNumber, String streetNumber) {

        this.id = id;

        this.firstName = firstName;

        this.lastName = lastName;

        this.age = age;

        this.houseNumber = houseNumber;

        this.streetNumber = streetNumber;

    }

    public String getId() {

        return id;

    }

    public String getFirstName() {

        return firstName;

    }

    public String getLastName() {

        return lastName;

    }

    public String getAge() {

        return age;

    }

    public String getHouseNumber() {

        return houseNumber;

    }

    public String getStreetNumber() {

        return streetNumber;

    }

}

Now, say the client of this code might want to create instance of Student with

– Only Mandatory fields

– All the Mandatory as well as optional fields

– Mandatory fields and one or more of the optional fields

Then the constructors for above scenarios will look like as below :

//Only Mandatory fields                                                       Student student2 = new Student("201", "firstName2", "surName2", null, null, null);

//All the Mandatory as well as optional fields                               Student student1 = new Student("101", "firstName1", "surName1", "16", "11", "2");

//Mandatory fields and one or more optional fields                           Student student3 = new Student("301", "firstName3", "surName3", "20", null, null);

Student student4 = new Student("301", "firstName4", "surName4", "20", "22", null);

Now, what is the problem with the above constructors?

Actually, there are multiple problems, like

– Client code has to unnecessarily pass null for all optional fields.

– Code readability is not good. As the number of parameters grow, it becomes difficult and error prone for client code to understand what needs to be passed at which position and later to read for the person who is going to maintain the code.

– When adjacent parameters are of same data type, you might accidently exchange their values which will go unnoticed at compile time but create some severe bug at run time. For example, developer can accidently interchange values for age and houseNumber.

So What can you do to solve these problems ?

Probably we can have look at the Telescoping constructor pattern.

Oracle Java Exam Prep, Oracle Java Tutorial and Material, Core Java, Oracle Java Learning, Oracle Java certification
In Telescoping constructor pattern, we create multiple constructor overloads starting with one with all mandatory fields and then with one optional field and then with two optional fields and so on until we have constructor with all fields.

Each constructor calls another constructor with one more optional field and passes the default value for the optional field(can be null or any other default you want to set) until the last constructor with all optional fields is called.

package com.blogspot.oraclejavacertified;

public class Student {

    //Mandatory fields

    private String id;

    private String firstName;

    private String lastName;

    //Optional fields

    private String age;

    private String houseNumber;

    private String streetNumber;

    public Student(String id, String firstName, String lastName) {

        this(id, firstName, lastName, "0");

    }

    public Student(String id, String firstName, String lastName, String age) {

        this(id, firstName, lastName, age, "0");

    }

    public Student(String id, String firstName, String lastName, String age, String houseNumber) {

        this(id, firstName, lastName, age, houseNumber, "0");

    }

    public Student(String id, String firstName, String lastName, String age, String houseNumber, String streetNumber) {

        this.id = id;

        this.firstName = firstName;

        this.lastName = lastName;

        this.age = age;

        this.houseNumber = houseNumber;

        this.streetNumber = streetNumber;

    }

}

Now let us see which problems telescoping constructor solved :

– Client code no longer has to pass null for all the optional fields as well.

– From client code perspective, readability is better.

But telescoping constructors comes with its own problems:

– What if more optional fields are added in Student class in future, then for each new field another constructor needs to be introduced.

– Still you need to carefully see all overloads of the constructor and choose the one which suits your requirement.

– Still if you have say age and streetNumber but houseNumber is not available, then you need to instantiate Student class like below, so still client need to pass in null value for optional field.

Student student = new Student("101", "firstName", "lastName", "35", null, "3");

– And still there is possibility of getting exchange of values of same data type, when the number of optional fields are too many.

We will see how builder pattern solves these problems later but for now let us discuss the other aspect which makes a case for using builder pattern.

You want objects of your class to be immutable :

If you don’t want state of your object to get changed once it has been created(and of course it has lots of fields), you can use build pattern, as builder pattern makes sure your object is immutable once it is created.

Advantages of Immutable classes :

– They are more reliable as it is known that their state is not going to change after creation.

– They are inherently thread safe and don’t need any synchronization.

– They make great candidates to be used as a key of a HashMap or to be put in a HashSet.

Now let us see implementation of Builder pattern by taking example of our Student class.

Builder pattern

– In builder pattern , you leave the responsibility of creating object or instantiating your class to Builder which is another class which has exactly same number of fields as your class whose objects builder is going to build.

– As your builder class is going to be used only for creating objects of your class and is not going to be used elsewhere, it is defined as static nested class within your class.

– You provide a builder’s constructor with only mandatory fields and then you provide methods(mutators) in builder to set the remaining optional fields. You can chain these methods as each of these method again returns Builder. Note that till now we are talking only about using builder constructor and using methods to set other optional fields and all these are still part of Builder object and we have not yet created actual Student object, so we are not concerned about immutability yet.

So in terms of code, we are here :

Student.StudentBuilder studentBuilder2 = ("2",                                                                               "Sachin", "Tendulkar").withAge("47");

Now all we need to do is call build() method of our builder on the created builder instance like below :

studentBuilder2.build()

which in turn calls private constructor of the Student class and passes “this” reference which is reference to the builder which is calling build() method.

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

In the constructor, values are copied from builder to the Student instance variables and a complete immutable student object is created.

private Student(StudentBuilder studentBuilder) {                                            
    id = studentBuilder.id;                                                            
    firstName = studentBuilder.firstName                                            
    lastName = studentBuilder.lastName;                                                
    age = studentBuilder.age;                                                        
    houseNumber = studentBuilder.houseNumber;                                        
    streetNumber = studentBuilder.streetNumber;                                
}

Builder pattern example


package com.test.builder;
 
public class Student {
    //Mandatory fields
    private final String id;
    private final String firstName;
    private final String lastName;
 
    //Optional fields
    private final String age;
    private final String houseNumber;
    private final String streetNumber;
 
    private Student(StudentBuilder studentBuilder) {
        id = studentBuilder.id;
        firstName = studentBuilder.firstName;
        lastName = studentBuilder.lastName;
        age = studentBuilder.age;
        houseNumber = studentBuilder.houseNumber;
        streetNumber = studentBuilder.streetNumber;
    }
 
    public String getId() {
        return id;
    }
 
    public String getFirstName() {
        return firstName;
    }
 
    public String getLastName() {
        return lastName;
    }
 
    public String getAge() {
        return age;
    }
 
    public String getHouseNumber() {
        return houseNumber;
    }
 
    public String getStreetNumber() {
        return streetNumber;
    }
 
    public static class StudentBuilder {
        //Mandatory fields
        private final String id;
        private final String firstName;
        private final String lastName;
 
        //Optional fields
        private String age;
        private String houseNumber;
        private String streetNumber;
 
        public StudentBuilder(String id, String firstName, String lastName) {
            this.id = id;
            this.firstName = firstName;
            this.lastName = lastName;
        }
 
        public StudentBuilder withAge(String age) {
            this.age = age;
            return this;
        }
 
        public StudentBuilder withHouseNumber(String houseNumber) {
            this.houseNumber = houseNumber;
            return this;
        }
 
        public StudentBuilder withStreetNumber(String streetNumber) {
            this.streetNumber = streetNumber;
            return this;
        }
 
        public Student build() {
            return new Student(this);
        }
     }
 
    @Override
    public String toString() {
        return "Student{" +
                "id='" + id + '\'' +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", age='" + age + '\'' +
                ", houseNumber='" + houseNumber + '\'' +
                ", streetNumber='" + streetNumber + '\'' +
                '}';
    }
}

and here is the test class :

package com.blogspot.oraclejavacertified;
 
public class TestStudentBuilder {
    public static void main(String[] args) {
        Student.StudentBuilder studentBuilder1 = new Student.StudentBuilder("1",                                              "Gauarv", "Bhardwaj");
        System.out.println(studentBuilder1.build());
 
        Student.StudentBuilder studentBuilder2 = new Student.StudentBuilder("2",                                             "Sachin", "Tendulkar").withAge("47");
        System.out.println(studentBuilder2.build());
    }
}

and here is the output:

Student{id='1', firstName='Gauarv', lastName='Bhardwaj', age='null', houseNumber='null', streetNumber='null'}
Student{id='1', firstName='Sachin', lastName='Tendulkar', age='47', houseNumber='null', streetNumber='null'}

Advantages of Builder Pattern :


– Client code is much more clean and readable. If we want to create object only with the mandatory fields ,we can just create builder instance with mandatory fields and then call build() method which will return us the Student object with only mandatory fields, however if we want to create Student object with some optional fields we can call respective methods like withAge() or withHouseNumber() and get Student object with all these fields as well. So we are not forcing client code to pass unnecessarily null values for the optional fields.

– Problem with getting values exchanged is also resolved as optional fields can be added by calling their respective methods which have clearly defined names.

– Object created using Builder pattern is immutable, as there are no setters in the Student class and also the constructor is private, so the only way to create Student object is via builder.

Disadvantages of Builder Pattern :


– Disadvantage is that you have to write lot of extra code for Builder class and as and when you need to add more fields, you need to add those fields both to your Student class as well as to your builder class. This is one of the reason that you should keep your Builder class within your class to be built as static nested class so that you don’t miss to add new field to builder as well .

Overall better code readability and immutability that Builder patter offers overweighs disadvantages it has, in my opinion.

Related Posts

0 comments:

Post a Comment