Effective Java - The Builder Pattern
Static factories and constructors share a limitation: they do not scale well to a large number of optional parameters.
Consider the case of a computer configuration builder. Some parts are essential in this build, while others are optional.
There are a number of ways that developers usually try to approach this problem. The first is telescopic constructors, where you need to create a constructor for each set of optional parameters. This approach has obvious disadvantages (ugly code, a lot of work). The second approach is to use JavaBeans, in which you call a parameterless constructor to create the object and then call setter methods to set each required and optional parameter. There are two problems with this approach:
- A JavaBean may be in an inconsistent state partway through its construction.
- This pattern precludes the possibility of making a class immutable.
However, there is a third approach that is very elegant and can create immutable objects. It is a form of the Builder pattern. Instead of making the desired object directly, the client calls a constructor (or static factory) with all the required parameters and gets a builder object. Here is what it looks like:
public class ComputerConfigurator {
private final int sizeOfRam;
private final int sizeOfHdd;
private final int processorSpeed;
private final int dedicatedGpuSpeed;
private final boolean waterCooling;
public static class Builder {
// Required params
private final int sizeOfRam;
private final int sizeOfHdd;
private final int processorSpeed;
// Optional params
private int dedicatedGpuSpeed;
private boolean waterCooling;
public Builder(int sizeOfRam, int sizeOfHdd, int processorSpeed) {
this.sizeOfRam = sizeOfRam;
this.sizeOfHdd = sizeOfHdd;
this.processorSpeed = processorSpeed;
}
public Builder dedicatedGpuSpeed(int val) {
dedicatedGpuSpeed = val;
return this;
}
public Builder waterCooling(boolean val) {
waterCooling = val;
return this;
}
public ComputerConfigurator build() {
return new ComputerConfigurator(this);
}
}
private ComputerConfigurator(Builder builder) {
sizeOfRam = builder.sizeOfRam;
sizeOfHdd = builder.sizeOfHdd;
processorSpeed = builder.processorSpeed;
dedicatedGpuSpeed = builder.dedicatedGpuSpeed;
waterCooling = builder.waterCooling;
}
}
The builder’s setter methods return the builder itself, allowing invocations to be chained. Here’s how the client code looks:
public class Main {
public static void main(String[] args) {
ComputerConfigurator firstComputerConfigurator = new ComputerConfigurator.
Builder(2, 40, 3000).
dedicatedGpuSpeed(1000).build();
ComputerConfigurator secondComputerConfigurator = new ComputerConfigurator.
Builder(2, 40, 3000).
dedicatedGpuSpeed(1000).waterCooling(true).build();
}
}
Enjoy Reading This Article?
Here are some more articles you might like to read next: