The Builder Design Pattern is one of the most widely used creational patterns in Java. It helps in constructing complex objects step-by-step, especially when a class has many optional parameters or configurations. While the traditional builder pattern is enough for most use cases, there’s a useful variation that combines the Classic Builder approach with a Director, bringing more flexibility and structure to the object creation process.
Understanding the Need
Suppose you are building a Computer object with multiple fields like CPU, RAM, Storage, and GPU. Some computers might be gaming PCs, others might be office machines, and each of them has different configurations. If you try to use constructors for all these variations, you’ll end up with too many overloaded constructors, making your code confusing and error-prone. The Builder pattern helps you build these objects in a readable, step-by-step manner, and the Director helps you define reusable configurations for specific types of objects.
Classic Builder Implementation
Let’s start with the classic nested builder version of the Computer class.
Here, we define a static inner Builder class that provides setter methods for each field and a build() method that returns the constructed object.
class Computer {
private String cpu;
private String ram;
private String storage;
private String gpu;
private Computer(Builder builder) {
this.cpu = builder.cpu;
this.ram = builder.ram;
this.storage = builder.storage;
this.gpu = builder.gpu;
}
public static class Builder {
private String cpu;
private String ram;
private String storage;
private String gpu;
public Builder setCpu(String cpu) {
this.cpu = cpu;
return this;
}
public Builder setRam(String ram) {
this.ram = ram;
return this;
}
public Builder setStorage(String storage) {
this.storage = storage;
return this;
}
public Builder setGpu(String gpu) {
this.gpu = gpu;
return this;
}
public Computer build() {
return new Computer(this);
}
}
@Override
public String toString() {
return "Computer [CPU=" + cpu + ", RAM=" + ram + ", Storage=" + storage + ", GPU=" + gpu + "]";
}
}
In this setup, you can create different computer objects by chaining method calls:
Computer gamingPC = new Computer.Builder()
.setCpu("Intel i9")
.setRam("32GB")
.setStorage("1TB SSD")
.setGpu("RTX 4090")
.build();
This approach works well when each object configuration is manually built in client code.
But what if we need to create standard configurations repeatedly, like gaming PCs, office PCs, or budget laptops?
That’s where the Director comes in.
Introducing the Director
The Director is responsible for managing the construction process. It defines the sequence and configuration of the building steps, while the Builder handles the actual object creation.
In our example, the Director will provide ready-made build methods for different types of computers.
class ComputerDirector {
public Computer buildGamingComputer() {
return new Computer.Builder()
.setCpu("Intel i9")
.setRam("32GB")
.setStorage("1TB SSD")
.setGpu("RTX 4090")
.build();
}
public Computer buildOfficeComputer() {
return new Computer.Builder()
.setCpu("Intel i5")
.setRam("16GB")
.setStorage("512GB SSD")
.setGpu("Integrated Graphics")
.build();
}
public Computer buildBudgetComputer() {
return new Computer.Builder()
.setCpu("Intel i3")
.setRam("8GB")
.setStorage("256GB SSD")
.setGpu("Integrated Graphics")
.build();
}
}
Using the Director in the Client Code
Now, the client doesn’t need to remember configurations.
They just need to tell the Director what kind of computer they want, and the Director uses the Builder to create it.
public class Main {
public static void main(String[] args) {
ComputerDirector director = new ComputerDirector();
Computer gamingPC = director.buildGamingComputer();
Computer officePC = director.buildOfficeComputer();
Computer budgetPC = director.buildBudgetComputer();
System.out.println(gamingPC);
System.out.println(officePC);
System.out.println(budgetPC);
}
}
Output:
Computer [CPU=Intel i9, RAM=32GB, Storage=1TB SSD, GPU=RTX 4090]
Computer [CPU=Intel i5, RAM=16GB, Storage=512GB SSD, GPU=Integrated Graphics]
Computer [CPU=Intel i3, RAM=8GB, Storage=256GB SSD, GPU=Integrated Graphics]
How the Director Complements the Builder
The combination of Director + Builder gives you the best of both worlds.
The Builder provides flexibility for creating custom objects, while the Director defines reusable construction recipes.
This makes your code more modular, reduces duplication, and separates how an object is built from what the final configuration is.
In large-scale systems, the Director can even take different builder implementations (for example, GamingBuilder, OfficeBuilder, ServerBuilder), allowing full polymorphism.
In simpler terms, the Director knows the process, while each Builder knows the details.
Key Takeaways
The Director with Classic Builder variation is a clean and structured approach when your application needs multiple standardized object configurations.
It maintains readability, improves code reuse, and follows the Single Responsibility Principle — the Builder builds, and the Director directs.
So the next time you’re creating different variants of complex objects, think beyond the simple builder — let the Director take control of the construction process, while your Builder focuses purely on object assembly.
Top comments (0)