5

Let's look at the Builder Pattern in Java - Knoldus Blogs

 2 years ago
source link: https://blog.knoldus.com/lets-look-at-the-builder-pattern-in-java/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
Reading Time: 5 minutes

In this blog, we are going to look at what the Builder Pattern is and how does it help us construct objects of classes easily. But before understanding that, we need to understand why did we come up with the Builder Pattern in the first place. So, let’s discuss the problem statement due to which the Builder Pattern came into existence.

The Problem Statement

We want to construct an object of a class. Pretty simple, right? We can just have a constructor to do that. Awesome! But, I want to construct an object of a class that has 20 arguments. I can still create a constructor for this class, but I will have to give 20 parameters to that constructor. But still, doable. Let’s take the difficulty up a notch. Now, out of these 20 arguments, only 2 are my required arguments, and 18 are optional, i.e., those are not necessarily required to create the object of the class. They can have some default values. Now, our constructor can still work here, but if the user constructing an object of our class only needs to set 2 required arguments and not the 18 optional arguments, then he will have to still pass all the 20 arguments, where, for the ones he doesn’t want to set, he will be passing default values. This is too much work for the user. This will be the problem statement that we will be trying to solve in this blog. So, let’s go ahead and look at the first approach.

Telescoping Constructor Pattern

The first approach that was devised to get around this problem, was the Telescoping Constructor Pattern. Let’s understand this pattern with an example.

public final class Person {

//required fields

private final String firstName;

private final String lastName;

//optional fields

private final String middleName;

private final String facebookId;

private final String twitterId;

public Person(String firstName, String lastName, String middleName, String facebookId, String twitterId) {

this.firstName = firstName;

this.lastName = lastName;

this.middleName = middleName;

this.facebookId = facebookId;

this.twitterId = twitterId;

}

public Person(String firstName, String lastName, String middleName, String facebookId) {

this(firstName, lastName, middleName, facebookId, "");

}

public Person(String firstName, String lastName, String middleName) {

this(firstName, lastName, middleName, "");

}

public Person(String firstName, String lastName) {

this(firstName, lastName, "");

}

}

As you can see above, we have created multiple constructors with a different number of parameters. We have a constructor with only the required parameters that we can call if we only want to set the required parameters, and the optional parameters will be set with default values. What happens here is chaining of constructor calls. We have multiple constructors calling another constructor while setting one more argument with the default value. Eventually, the calls end up with the constructor which has all the parameters and in that constructor, we set all the values to the passed values. Now, the user just needs to find a suitable constructor according to his use case, and use it. For example, if the user wants to set firstName, lastName and middleName, then he can call the constructor at line 22 and other parameters will be set to default values. But, what if I want to set facebookId but not middleName. For this purpose, we will not be able to create a new constructor, as there is already one constructor with three String parameters. So, the user will have to call the constructor at line 18 and will have to pass middleName‘s default value as well, although he doesn’t need to set it. So, that is one disadvantage of this pattern.

Furthermore, this was a small example so that you can grasp the concept easily, and so it only had 5 arguments. Just imagine the code base of a class with 20 parameters with this approach. It would be too much. Plus, with identical types of parameters (as in this example), the user might mistakenly reverse the order of two parameter values while passing them in the constructor. The compiler won’t complain, but the program will misbehave at runtime. So, this approach had a lot of limitations. To overcome these limitations, came another approach, known as the JavaBeans pattern.

The JavaBeans Pattern

The JavaBeans approach dictates that the class should have a public no-arg constructor and public setter and getter methods. Let’s look at an example of this approach.

public final class Person {

private String firstName;

private String lastName;

private String middleName = "";

private String facebookId = "";

private String twitterId = "";

public Person() {}

//Setters

public void setFirstName(String firstName) {

this.firstName = firstName;

}

public void setLastName(String lastName) {

this.lastName = lastName;

}

public void setMiddleName(String middleName) {

this.middleName = middleName;

}

public void setFacebookId(String facebookId) {

this.facebookId = facebookId;

}

public void setTwitterId(String twitterId) {

this.twitterId = twitterId;

}

}

Using this approach, we can create an object of the Person class without passing any values, as it has a no-arg default constructor. Then, after we have created an object, we can call the appropriate setter methods to assign values to the class variables. This seems pretty clean, easy and very readable as well. It does not have any limitations of the telescoping constructor pattern. But it has some serious drawbacks of its own.

First of all, since we are setting values in the setter methods, we are basically allowing the user to mutate the class variables. This introduces mutability in our class. Another drawback is that the class object now will be fully constructed in multiple lines. What I mean by that is, that, first of all, I will create an empty object, then I will call setter to set one parameter, then another setter to set another parameter, and so on. So, the class is being fully constructed in multiple lines of code. This means that the object can be in an invalid state while being constructed. Just look at the below example.

public class JavaBeansExample {

public static void main(String[] args) {

Person person = new Person();

person.setFirstName("Akshansh");

person.setFacebookId("akshansh.jain.95");

}

}

Here, I am setting firstName and facebookId of the person, but not lastName, although it is a required field. Now, this object will be having lastName as null. Here, the compiler won’t complain but the application will misbehave at runtime. Lastly, we will look at the Builder Pattern to overcome these challenges.

The Builder Pattern

We will finally look at The Builder Pattern. In this pattern, we have a static inner class in the class whose object we want to create. We call this inner class’s constructor and get its object, and then we finally call a build() method to get the object of the class we wanted to construct. Let’s look at it with an example.

public final class Person {

private final String firstName;

private final String lastName;

private final String middleName;

private final String facebookId;

private final String twitterId;

public static class Builder {

private final String firstName;

private final String lastName;

private String middleName = "";

private String facebookId = "";

private String twitterId = "";

public Builder(String firstName, String lastName) {

this.firstName = firstName;

this.lastName = lastName;

}

public Builder middleName(String middleName) {

this.middleName = middleName;

return this;

}

public Builder facebookId(String facebookId) {

this.facebookId = facebookId;

return this;

}

public Builder twitterId(String twitterId) {

this.twitterId = twitterId;

return this;

}

public Person build() {

return new Person(this);

}

}

private Person(Builder builder) {

this.firstName = builder.firstName;

this.lastName = builder.lastName;

this.middleName = builder.middleName;

this.facebookId = builder.facebookId;

this.twitterId = builder.twitterId;

}

}

Here we have a static class Builder. It has a constructor that takes the required fields of our class. It provides setter-like methods so that the user can call them during construction and set each variable individually. It also has default values for optional variables, so if the value is never set by the user, it will still have the default value. After providing values of the variables that user wanted to use, he will simply call a parameterless build() method, which will return him an object of Person.

As you can notice here, the Person class has a private constructor, so it is not accessible by the outside world. It will only be accessible through the inner static class Builder. Here, we are providing the required parameters in the constructor of the Builder class, so we are ensuring that there can never be an object of Person class in an invalid state. The class also becomes immutable, as apparent by the code. It might be a little verbose, but for a large number of variables, 20 for example, this will be quite easy to read and write as compared to other approaches.

Furthermore, the methods in the Builder to set the variables’ values returns Builder object only, thus promoting a fluent API. This how the user can create an object of this class –

public class BuilderExample {

public static void main(String[] args) {

Person person = new Person.Builder("Akshansh", "Jain").facebookId("akshansh.jain.95").build();

}

}

It has a very minute impact on the performance on the application as you have to create a Builder object before you can go ahead and create the object of the original class, but it is hardly noticeable in practice, although it could be a problem in a performance-critical situation.

This is all for this blog. I hope that now you have much more clarity about The Builder Pattern and how to implement it. Thanks for reading!

References –

  1. Effective Java – Book by Joshua Bloch

Knoldus-Scala-Spark-Services


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK