The Builder Design Pattern is a creational pattern that provides a way to construct complex objects step by step. It separates the construction of a complex object from its representation, allowing the same construction process to create different representations.
Components of the Builder Design Pattern:
- Builder: An abstract class or interface that defines the steps for creating parts of a complex object.
- ConcreteBuilder: Implements the Builder interface to construct and assemble parts of the product.
- Product: The complex object being built.
- Director: Constructs an object using the Builder interface. It defines the order in which to execute the building steps.
Example 1:
Let’s create a simple example where we build a Computer
object with various components like CPU, RAM, and storage. We will use the Builder Pattern to construct the Computer
object step by step.
1. Product:
// Product class
class Computer {
private String CPU;
private String RAM;
private String storage;
public void setCPU(String CPU) {
this.CPU = CPU;
}
public void setRAM(String RAM) {
this.RAM = RAM;
}
public void setStorage(String storage) {
this.storage = storage;
}
@Override
public String toString() {
return "Computer [CPU=" + CPU + ", RAM=" + RAM + ", Storage=" + storage + "]";
}
}
2. Builder:
// Builder interface
interface ComputerBuilder {
void buildCPU();
void buildRAM();
void buildStorage();
Computer getComputer();
}
3. ConcreteBuilder:
// ConcreteBuilder class
class GamingComputerBuilder implements ComputerBuilder {
private Computer computer;
public GamingComputerBuilder() {
this.computer = new Computer();
}
@Override
public void buildCPU() {
computer.setCPU("High-performance CPU");
}
@Override
public void buildRAM() {
computer.setRAM("16GB RAM");
}
@Override
public void buildStorage() {
computer.setStorage("1TB SSD");
}
@Override
public Computer getComputer() {
return this.computer;
}
}
4. Director:
// Director class
class ComputerDirector {
private ComputerBuilder builder;
public ComputerDirector(ComputerBuilder builder) {
this.builder = builder;
}
public void constructComputer() {
builder.buildCPU();
builder.buildRAM();
builder.buildStorage();
}
}
5. Client Code:
public class BuilderPatternDemo {
public static void main(String[] args) {
// Create a builder for a gaming computer
ComputerBuilder gamingBuilder = new GamingComputerBuilder();
// Create a director with the gaming computer builder
ComputerDirector director = new ComputerDirector(gamingBuilder);
// Construct the computer
director.constructComputer();
// Get the built computer
Computer gamingComputer = gamingBuilder.getComputer();
System.out.println(gamingComputer);
}
}
Explanation:
- Product:
Computer
is the complex object being built. It has parts like CPU, RAM, and storage. - Builder:
ComputerBuilder
defines the steps to build aComputer
. - ConcreteBuilder:
GamingComputerBuilder
implements theComputerBuilder
interface to create a specific type of computer. - Director:
ComputerDirector
uses the builder to construct theComputer
step by step. - Client Code: Demonstrates how to use the
ComputerDirector
andGamingComputerBuilder
to build aComputer
and output its details.
Example 2: Custom Builder Implementation
Let’s create a House
class with attributes like walls, windows, and roof, and provide a builder class to construct House
objects.
1. Define the Product Class (House
):
public class House {
private final String walls;
private final String windows;
private final String roof;
// Private constructor to enforce the use of Builder
private House(Builder builder) {
this.walls = builder.walls;
this.windows = builder.windows;
this.roof = builder.roof;
}
// Getters (No setters to ensure immutability)
public String getWalls() {
return walls;
}
public String getWindows() {
return windows;
}
public String getRoof() {
return roof;
}
@Override
public String toString() {
return "House [Walls=" + walls + ", Windows=" + windows + ", Roof=" + roof + "]";
}
// Static Builder class
public static class Builder {
private String walls;
private String windows;
private String roof;
// Setter methods return the builder itself to allow method chaining
public Builder setWalls(String walls) {
this.walls = walls;
return this;
}
public Builder setWindows(String windows) {
this.windows = windows;
return this;
}
public Builder setRoof(String roof) {
this.roof = roof;
return this;
}
// Build method to create a House instance
public House build() {
return new House(this);
}
}
}
2. Client Code:
public class BuilderPatternCustomDemo {
public static void main(String[] args) {
// Use the builder to create a House object
House house = new House.Builder()
.setWalls("Brick walls")
.setWindows("Glass windows")
.setRoof("Tile roof")
.build();
// Output the house details
System.out.println(house);
}
}
Explanation:
- Product Class (
House
): Contains attributes forwalls
,windows
, androof
. The constructor is private to enforce the use of the builder for object creation. - Builder Class:
- Contains fields that match those of the
House
class. - Provides setter methods (
setWalls()
,setWindows()
,setRoof()
) that return the builder instance (this
) to allow method chaining. - The
build()
method constructs aHouse
object using the builder instance.
- Contains fields that match those of the
- Client Code: Uses the
House.Builder
to set the attributes and build theHouse
object. It demonstrates method chaining and immutability of theHouse
class.
This custom builder pattern implementation provides a flexible and readable way to create complex objects, similar to what Lombok’s @Builder
provides with automatic code generation.
Benefits of the Builder Design Pattern:
- Separation of Concerns: Separates the construction process from the representation of the object, allowing different representations.
- Flexibility: Allows complex objects to be constructed in a step-by-step manner.
- Immutability: Can help create immutable objects (objects whose state cannot be changed after creation) by controlling the construction process.
Use Cases of the Builder Design Pattern:
- Complex Object Creation: Building complex objects with many optional components or configurations.
- Fluent APIs: Creating builder APIs that provide a fluent interface for constructing objects.
- Immutability: Building immutable objects that require complex initialization.
The Builder Pattern is particularly useful when dealing with complex objects that require a specific order of creation or when you need to construct objects with a large number of possible configurations.