Creating complex software objects that require many parameters can quickly lead to messy, hard-to-read code. In software engineering, the Builder Design Pattern solves this problem by separating the construction of a complex object from its representation, letting you build objects step-by-step.
In this guide, we will break down the Builder pattern using a simple robot builder helper analogy and trace the execution flow of a custom company builder in Java.
Imagine you want to build a custom toy company out of Legos. If you tried to configure everything at once in a single step, you would have to remember a long, confusing list of names and numbers in the exact right order. One tiny mistake, and your toy company's CEO might be registered as its location address!
The Builder Pattern introduces a robot helper to handle this systematically:
- The Must-Haves: First, the robot builder helper asks for the absolute essentials: the company's name, location, and CEO. You cannot start building without these.
- The Checklist (Method Chaining): Next, you tell the robot the optional details you want to add (like profits, revenue, or a list of directors) one-by-one. Each time you give an instruction, the robot says, "Got it! What's next?" (this is why each method returns the builder itself).
- Construction: When you are done, you say "Build!". The robot locks all the settings in place, sets up the company, and hands you the completed toy company, which is now locked so it cannot be accidentally messed up later.
Walkthrough of the Main Method Scenario
Let's trace how the program executes step by step from the entry point of the main program:
The program enters the main method and starts the construction line by specifying the minimum essential details inside the constructor of the builder:
new Organization.OrganizationBuilder("Pepsi", "Nevada", "Indira Noori")
This creates an instance of the helper class, storing the initial required variables internally.
Next, it adds optional details one-by-one. Because each method returns the same builder instance (return this;), we chain the calls together in a single sentence:
.setCtoName("ABC")
.setProfits(1000.0)
.setRevenue(40000.0)
.setMultinational(true)
.setListOfDirectors(Arrays.asList("A", "B", "C"))
.setHeadQuartersAddress("1 Pepsico Way")
Each method modifies its corresponding builder field and hands the builder back for the next configuration.
Finally, the program triggers the construction call:
.build();
Behind the scenes, the builder calls the private Organization constructor, passing itself (this) as the checklist parameter. The Organization extracts all values from the builder into its own private fields. Since the Organization class lacks any setter methods, the created company is now completely safe and immutable.
Java Implementation
Below is the complete Java code demonstrating the Builder Design Pattern implementation:
package io.practise.accolite;
import java.util.*;
public class TestBuilderDesignPattern {
public static void main(String[] args) {
Organization organization = new Organization
.OrganizationBuilder("Pepsi", "Nevada", "Indira Noori")
.setCtoName("ABC")
.setProfits(1000.0)
.setRevenue(40000.0)
.setMultinational(true)
.setListOfDirectors(Arrays.asList("A", "B", "C"))
.setHeadQuartersAddress("1 Pepsico Way")
.build();
System.out.println(organization);
}
}
class Organization {
private String orgName;
private String orgAddress;
private String ceoName;
private String headQuartersAddress;
private String ctoName;
private List<String> listOfDirectors;
private double revenue;
private double profits;
private boolean isMultinational;
public Organization(OrganizationBuilder organizationBuilder) {
this.orgName = organizationBuilder.orgName;
this.orgAddress = organizationBuilder.orgAddress;
this.ceoName = organizationBuilder.ceoName;
this.headQuartersAddress = organizationBuilder.headQuartersAddress;
this.ctoName = organizationBuilder.ctoName;
this.listOfDirectors = organizationBuilder.listOfDirectors;
this.revenue = organizationBuilder.revenue;
this.profits = organizationBuilder.profits;
this.isMultinational = organizationBuilder.isMultinational;
}
@Override
public String toString() {
return "Organization{" + "orgName='" + orgName + '\'' + ", orgAddress='" + orgAddress + '\'' + ", ceoName='" + ceoName + '\'' + ", headQuartersAddress='" + headQuartersAddress + '\'' + ", ctoName='" + ctoName + '\'' + ", listOfDirectors=" + listOfDirectors + ", revenue=" + revenue + ", profits=" + profits + ", isMultinational=" + isMultinational + '}';
}
public static class OrganizationBuilder {
private String orgName;
private String orgAddress;
private String ceoName;
private String headQuartersAddress;
private String ctoName;
private List<String> listOfDirectors;
private double revenue;
private double profits;
private boolean isMultinational;
public OrganizationBuilder(String orgName, String orgAddress, String ceoName) {
this.orgName = orgName;
this.orgAddress = orgAddress;
this.ceoName = ceoName;
}
public OrganizationBuilder setHeadQuartersAddress(String headQuartersAddress) {
this.headQuartersAddress = headQuartersAddress;
return this;
}
public OrganizationBuilder setCtoName(String ctoName) {
this.ctoName = ctoName;
return this;
}
public OrganizationBuilder setListOfDirectors(List<String> listOfDirectors) {
this.listOfDirectors = listOfDirectors;
return this;
}
public OrganizationBuilder setRevenue(double revenue) {
this.revenue = revenue;
return this;
}
public OrganizationBuilder setProfits(double profits) {
this.profits = profits;
return this;
}
public OrganizationBuilder setMultinational(boolean multinational) {
isMultinational = multinational;
return this;
}
public Organization build() {
return new Organization(this);
}
}
}
Conclusion
The Builder pattern is an elegant solution to the "telescoping constructor" anti-pattern (where you have constructors with 2, 3, 4, or 10 parameters). By delegating construction details to a dedicated static inner helper class, you achieve highly readable, expressive, and thread-safe object instantiations in your codebase.