Problem: Whenever the creation of a complex object requires a large number of attributes, calling a parameterized constructor or having several constructors with different combinations is cumbersome. It is hard to manage so many constructors.
In Java, constructors are used to construct objects and constructors can take parameters to initialize the class fields which the object utilizes. If no constructor is provided then Java itself uses the default constructor, also called the no-argument constructor, to build the object with default values like String variables(fields) are initialized with the “NULL” literal. Parameterized constructors give freedom to users to build objects with their own choices.
However, devs say that there is only one constructor to initialise all the parameters, but what if only two are mandatory instead of 10 parameters? Then we need more constructors to create objects using different combination parameters. This problem is called the telescoping constructors problem.
Suppose, to build cake objects (vanilla cake, chocolate cake) we create a class cake that needs compulsory items like Milk, Egg, floor, etc. and also have optional items like fruit, cherry, etc. If we observe that chocolate, vanilla, fruit, cherry etc are optional items. So, creating every possible cake object combination requires several constructors which is very hard to maintain.
Solution: The builder pattern rescues the problem by constructing a complex object step-by-step. Initialising optional parameters does not make sense on every object creation. Builder pattern aims to separate the creation of objects using mandatory parameters and optional parameters are initialized only if required. Builder pattern also improves readability and maintainability by providing setters by their name which you will understand after checking code.
The builder pattern is another creational design pattern to propose a solution for building complex objects. Builder pattern states that “separate the instantiation process by using another builder object”, which we will understand in the code sections.
Let’s start creating the builder class:
1. All the fields are declared private since outer objects have access to them directly.
public class Employee {
private final String name; // Required
private final int age; // Optional
private final String department; // Optional
private final double salary; // Optional
private final String address; // Optional
// Private constructor to enforce object creation through builder
private Employee(EmployeeBuilder builder) {
this.name = builder.name;
this.age = builder.age;
this.department = builder.department;
this.salary = builder.salary;
this.address = builder.address;
}
// Getters (No Setters to make Employee immutable)
public String getName() { return name; }
public int getAge() { return age; }
public String getDepartment() { return department; }
public double getSalary() { return salary; }
public String getAddress() { return address; }
// Override toString() for better output
@Override
public String toString() {
return "Employee{name='" + name + "', age=" + age +
", department='" + department + "', salary=" + salary +
", address='" + address + "'}";
}
// Static Builder Class
public static class EmployeeBuilder {
private final String name; // Required
private int age;
private String department;
private double salary;
private String address;
// Constructor for mandatory fields
public EmployeeBuilder(String name) {
this.name = name;
}
// Methods for setting optional fields
// (Returning Builder for Chaining)
public EmployeeBuilder setAge(int age) {
this.age = age;
return this;
}
public EmployeeBuilder setDepartment(String department) {
this.department = department;
return this;
}
public EmployeeBuilder setSalary(double salary) {
this.salary = salary;
return this;
}
public EmployeeBuilder setAddress(String address) {
this.address = address;
return this;
}
// Build Method to create Employee object
public Employee build() {
return new Employee(this);
}
}
}
2. The Client Code
public class BuilderPatternExample {
public static void main(String[] args) {
// Creating Employee Object using Builder
Employee emp1 = new Employee.EmployeeBuilder("John Doe")
.setAge(30)
.setDepartment("Engineering")
.setSalary(85000)
.setAddress("1234 Elm Street")
.build();
Employee emp2 = new Employee.EmployeeBuilder("Alice Smith")
.setAge(28)
.setDepartment("HR")
.build(); // No Salary and Address provided
System.out.println(emp1);
System.out.println(emp2);
}
}
Interview point, you can answer in the below manner.
- Immutability: Objects are created immutable due to no setters and a private constructor.
- Method Chaining: Enabled setting property values in a chained manner.
- Encapsulation: Object creation is separated using EmployeeBuilder and the Employee class is clean.
- Readability and Maintainability: Code is easily readable and easily can be modified.
Check out the Part second “Rules to Build Builder Pattern in Java“.