The Builder Design Pattern in Java
Want to learn more about the Builder design pattern in Java. Take a look at this post on using the Builder design pattern and why we need it.
Join the DZone community and get the full member experience.
Join For FreeToday, we will look into Builder pattern in Java. The Builder pattern is a creational pattern that can be used to create and configure objects.
Why Do We Need the Builder Pattern?
Suppose that you have a class with a handful of instance attributes, such as the Employee class shown below. In this class, some of the instance attributes are required while the rest are optional. What kind of constructors should you write for such a class? A first option would be to have a constructor that only takes the required attributes as parameters, one that takes all the required attributes plus the first optional one, another one that takes two optional attributes, and so on ( this is called telescoping constructor pattern). Then, your class will look something like below:
public class Employee {
private final String firstName; //required
private final String lastName; //required
private final int age; //required
private final int personalId; //required
private final String phone; //optional
private final String address; //optional
private final String mail; //optional
public Employee(String firstName, String lastName, int age, int personalId) {
this(firstName, lastName, age, personalId, "", "");
}
public Employee(String firstName, String lastName, int age, int personalId, String phone) {
this(firstName, lastName, age, personalId, phone, "");
}
public Employee(String firstName, String lastName, int age, int personalId, String phone, String address) {
this(firstName, lastName, age, personalId, phone, address, "");
}
public Employee(String firstName, String lastName, int age, int personalId, String phone, String address, String mail) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.personalId = personalId;
this.phone = phone;
this.address = address;
this.mail = mail;
}
}
As developers, we offer a solution for clients to construct objects from the Employee class. But, clients may return with a bunch of questions for us, including:
- Which constructor should I invoke? The one with four parameters or the one with five or six?
- What is the default value for those optional parameters if I don’t pass a value for each?
- If I want to pass values only for mail and phone but not for address, how can I fulfill this requirement?
- What will happen if I mistakenly pass the value intended for the address to mail (compile may not complain about it because they have the same type)?
You might be thinking that the JavaBean pattern could be the second alternative solution to the above questions. In the JavaBeans pattern, we call a no-arg constructor to create an object and then call the setter method on this object to set each required parameter value and each optional parameter of your interest (as shown below). It is no surprise that the JavaBean pattern increases the readability of code and the flexibility of object construction. But, this solution has two big disadvantages: the first one is that the object state will be inconsistent unless all the attributes are set explicitly; the second one is that the JavaBean pattern makes a class mutable and requires extra efforts of developers to ensure thread safety.
Note: the final modifier has to be removed when using the JavaBean Pattern.
public class Employee {
private String firstName; //required
private String lastName; //required
private int age; //required
private int personalId; //required
private String phone; //optional
private String address; //optional
private String mail; //optional
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setAge(int age) {
this.age = age;
}
public void setPersonalId(int personalId) {
this.personalId = personalId;
}
public void setPhone(String phone) {
this.phone = phone;
}
public void setAddress(String address) {
this.address = address;
}
public void setMail(String mail) {
this.mail = mail;
}
}
Now we come to the third alternative solution: The Builder Pattern. The builder pattern takes the advantages of the safety of telescoping constructor pattern and readability of JavaBean pattern.
What Is the Builder Pattern?
First, Let’s see how we can implement a builder design pattern.
- First of all, you need to create a public static nested class, which has all the instance attributes from the outer class. The naming convention for Builder usually is that and if the class name is
Employee
, then the builder class should be named asEmployeeBuilder
. - The outer class
Employee
should have a private constructor that takes aEmployeeBuilder
object as its argument. - The builder class should have a public constructor with all the required attributes as parameters and these required attributes are defined as "final," which have setter methods to set the optional parameters. It should return the same Builder object after setting the optional attribute.
- The final step is to provide a
build()
method in the builder class that will return the outer class object to the client. Thisbuild()
method will call the private constructor in the outer class, passing the Builder object itself as the parameter to this private constructor. - The
Employee
class has only getter methods and no public constructor. So, the only way to get an Employee object is through the nestedEmpolyeeBuilder
class.
The redesigned class using Builder pattern will look like this:
package main.builderPattern;
public class Employee {
private final String firstName; //required
private final String lastName; //required
private final int age; //required
private final int personalId; // required
private final String phone; //optional
private final String address; //optional
private final String mail; //optional
public static class EmployeeBuilder {
private final String firstName; //required
private final String lastName; //required
private final int age; //required
private final int personalId; // required
private String phone; //optional
private String address; //optional
private String mail; //optional
public EmployeeBuilder(String firstName, String lastName, int age, int personalId) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.personalId = personalId;
}
public EmployeeBuilder setAddress(String address) {
this.address = address;
return this;
}
public EmployeeBuilder setPhone(String phone) {
this.phone = phone;
return this;
}
public EmployeeBuilder setMail(String mail) {
this.mail = mail;
return this;
}
public Employee build() {
// call the private constructor in the outer class
return new Employee(this);
}
}
private Employee(EmployeeBuilder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.personalId = builder.personalId;
this.phone = builder.phone;
this.address = builder.address;
this.mail = builder.mail;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
public int getPersonalId() {
return personalId;
}
public String getPhone() {
return phone;
}
public String getAddress() {
return address;
}
public String getMail() {
return mail;
}
}
We can create an Employee
object from client code as follows:
public class EmployeeTest {
public static void main(String[] args) {
Employee employee = new Employee.EmployeeBuilder("Cristiano", "Ronaldo", 33, 7)
.setPhone("0045-1234556")
.setAddress("Juventus")
.setMail("CR@Juventus.org").build();
}
}
You can see that client code is easy to write and read. But, nothing is perfect; the Builder pattern has its own disadvantages, like being more verbose than the telescoping constructor pattern.
Summary
Keep in mind that if your class has multiple attributes with the same type or optional when you design your class, it is time to consider the Builder Pattern. If now, you may want to add more parameters to your class, then it is never too late to consider refactoring your code with the Builder pattern.
To wrap up this post, I would like to cite some words from Joshua Bloch's Effective Java:
" The Builder pattern is a good choice when degsigning classes whose constructors or static factories would have more than a handful of parameters, espectially if many of the parameters are optional or of identical type"
That's it for now. Feel free to drop a comment if you have anything to ask or to add!
Opinions expressed by DZone contributors are their own.
Comments