Builder Pattern
The What
The Builder Pattern is a creational design pattern used to construct complex objects step by step. This pattern allows you to produce different types and representations of an object using the same construction code. Essentially, the Builder Pattern separates the construction of a complex object from its representation, enabling you to create different types and representations of an object using the same construction process.
The General Problem It Tries to Solve
When constructing complex objects, such as those with many parameters or nested structures, code can become unwieldy and difficult to manage. Traditional approaches like telescoping constructors (constructors with many parameters) can make the code hard to read and maintain. This is especially true when not all parameters are always needed, leading to constructors with many optional parameters.
Example of the General Problem
Consider a scenario where we need to create different types of computers. A computer may have many optional features like a graphics card, sound card, and additional peripherals. Using traditional constructors, you might end up with something like this:
class Computer {
private final String CPU;
private final String RAM;
private final int storage;
private final boolean hasGraphicsCard;
private final boolean hasSoundCard;
public Computer(String CPU, String RAM, int storage) {
this(CPU, RAM, storage, false, false);
}
public Computer(String CPU, String RAM, int storage,
boolean hasGraphicsCard) {
this(CPU, RAM, storage, hasGraphicsCard, false);
}
public Computer(String CPU, String RAM, int storage,
boolean hasGraphicsCard, boolean hasSoundCard) {
this.CPU = CPU;
this.RAM = RAM;
this.storage = storage;
this.hasGraphicsCard = hasGraphicsCard;
this.hasSoundCard = hasSoundCard;
}
// Getters and other methods...
}
This approach is cumbersome and does not scale well as the number of optional parameters increases.
Generalizing the When
The Builder Pattern is useful when:
The How
Here’s a step-by-step guide on how to implement the Builder Pattern:
class Computer {
private final String CPU;
private final String RAM;
private final int storage;
private final boolean hasGraphicsCard;
private final boolean hasSoundCard;
private Computer(ComputerBuilder builder) {
this.CPU = builder.CPU;
this.RAM = builder.RAM;
this.storage = builder.storage;
this.hasGraphicsCard = builder.hasGraphicsCard;
this.hasSoundCard = builder.hasSoundCard;
}
@Override
public String toString() {
return "Computer with CPU: " + CPU + ", RAM: " + RAM + ", storage: " + storage + "GB" +
(hasGraphicsCard ? ", a graphics card" : "") +
(hasSoundCard ? ", and a sound card" : "");
}
}
2. Create the Builder Interface: This interface will declare the construction steps.
领英推荐
interface ComputerBuilder {
ComputerBuilder setCPU(String CPU);
ComputerBuilder setRAM(String RAM);
ComputerBuilder setStorage(int storage);
ComputerBuilder setGraphicsCard(boolean hasGraphicsCard);
ComputerBuilder setSoundCard(boolean hasSoundCard);
Computer build();
String getCPU();
String getRAM();
int getStorage();
boolean hasGraphicsCard();
boolean hasSoundCard();
}
3. Implement Concrete Builders: These classes will provide different implementations of the construction steps.
class ConcreteComputerBuilder implements ComputerBuilder {
private String CPU;
private String RAM;
private int storage;
private boolean hasGraphicsCard;
private boolean hasSoundCard;
@Override
public ComputerBuilder setCPU(String CPU) {
this.CPU = CPU;
return this;
}
@Override
public ComputerBuilder setRAM(String RAM) {
this.RAM = RAM;
return this;
}
@Override
public ComputerBuilder setStorage(int storage) {
this.storage = storage;
return this;
}
@Override
public ComputerBuilder setGraphicsCard(boolean hasGraphicsCard) {
this.hasGraphicsCard = hasGraphicsCard;
return this;
}
@Override
public ComputerBuilder setSoundCard(boolean hasSoundCard) {
this.hasSoundCard = hasSoundCard;
return this;
}
@Override
public Computer build() {
return new Computer(this);
}
@Override
public String getCPU() {
return CPU;
}
@Override
public String getRAM() {
return RAM;
}
@Override
public int getStorage() {
return storage;
}
@Override
public boolean hasGraphicsCard() {
return hasGraphicsCard;
}
@Override
public boolean hasSoundCard() {
return hasSoundCard;
}
}
4. Create a Director (Optional): This class defines the order of construction steps.
class ComputerDirector {
public Computer buildGamingComputer(ComputerBuilder builder) {
return builder.setCPU("Intel i9")
.setRAM("32GB")
.setStorage(1000)
.setGraphicsCard(true)
.setSoundCard(true)
.build();
}
public Computer buildOfficeComputer(ComputerBuilder builder) {
return builder.setCPU("Intel i5")
.setRAM("16GB")
.setStorage(500)
.setGraphicsCard(false)
.setSoundCard(false)
.build();
}
}
5. Client Code: The client code creates both the builder and the director objects and initiates the construction process.
public class Main {
public static void main(String[] args) {
ComputerBuilder builder = new ConcreteComputerBuilder();
ComputerDirector director = new ComputerDirector();
Computer gamingComputer = director.buildGamingComputer(builder);
Computer officeComputer = director.buildOfficeComputer(builder);
System.out.println(gamingComputer);
System.out.println(officeComputer);
}
}
UML diagram
Consequences and Tradeoffs
The Builder Pattern comes with its pros and cons.
Pros:
Cons:
Conclusion
The Builder Pattern is a powerful design pattern that helps manage the construction of complex objects. By separating the construction process from the object’s representation, it offers flexibility and scalability, making the code more readable and maintainable. Using our Computer example, we demonstrated how to implement the Builder Pattern in Java, highlighting its benefits and tradeoffs. With the Builder Pattern, you can construct complex objects in a more structured and manageable way.
Thank you