There are three key features of the builder pattern that need to exist before the pattern becomes recognizable.
- the class you are tying to build should have a lot of properties/fields that you would normally want to pass as a constructor
- you want those properties to be 'immutable' - constant for the life of the car (and this implies that there should not be 'setter' methods for those values)
- the class is constructed from many places so having a more convenient system is... convenient
In your case, you have three parameters, the color
, wheels
, and speed
for the car. These things can't be changed after the Car is built. You want a Car that has getColor()
, but not setColor(...)
.
The non-builder way to do this would be to have a single constructor that takes all the parameters:
public Car(String color, String wheels, int speed) {
....
}
and then call that constructor from everywhere. If there are a lot more parameters though, it gets messy to call the constructor:
Car mine = new Car(view.getCarColorDropDown().getSelectedValue(), view.getCarWheelsOptionRadioButton().getSelectedValue(), view.getCarSpeedSpinner().getValue());
Now imagine if you also want to select an interior color, automatic, or manual transmission, etc.
What happens when you have a lot of inputs is that it becomes hard to 'align' the values with the parameters of the class.... you have to cross reference them, and count the values to make sure you are putting the color in to the color parameter, etc.
Finally, what if you want some default values as well... or you need to validate combinations of choices.... like, you cannot select wheels
'abc' and not have speed
> 150? Validating that all in the constructor is ... hard.
The builder class is the solution to this. It makes the construction of the Car
class a sequential process... you add the data to a builder, it accumulates, validates, and isolates the inputs until you have captured all the information you need. Making sure the right parameters are given the right values is easy because the method has just one argument, and a logical name: setColor(view.getCarColorDropDown().getSelectedValue());
. Then, the builder organises the data in to a single call to the constructor (often private) of the Car class.
It is common to make the Builder class a sub-class of the class that is built, too.
Putting this all together, the builder has special permissions to access the Car constructor, the builder accumulates and validates the inputs in to changable values. It then calls the ugly constructor itself, creating a single, immutable class instance, and only one place in the code has to have the ugliness.
public class Car {
public static Builder builder() {
return new Builder();
}
// static inner class builder
public static class Builder {
private String builderColor = "white"; // default color
private String builderWheels = "round"; // default wheels
private int builderSpeed = 100; // default speed
private Builder() {
// inaccessible constructor, does nothing
}
public String getColor() {
return builderColor;
}
public void setColor(String color) {
// validate color....
this.builderColor = color;
}
public .... // more setters/getters
public Car build() {
return new Car(builderColor, builderWheels, builderSpeed);
}
}
// final (immutable) fields.
private final String color;
private final String wheels;
private final int speed;
// private constructor, only the builder calls this....
private Car(String color, String wheels, int speed) {
this.color = color;
this.wheels = wheels;
this.speed = speed;
}
public String getColor() {
return color;
}
.... // other getters
}
Now, how does one use the builder? Like this:
Car.Builder builder = Car.builder();
builder.setColor("red");
builder.setWheels("rwd");
builder.setSpeed(200);
Car mine = builder.build();
An often overlooked feature of a builder is that it is reusable as well, so, the above code can create many cars (it is a template of sorts)....
Car[] productionLine = new Car[100];
for (int i = 0; i < productionLine.length; i++) {
productionLine[i] = builder.build();
}
Or, you can change just small factors, like the VIN number... :
int vin = 123456000;
for (int i = 0; i < productionLine.length; i++) {
builder.setVIN(vin++);
productionLine[i] = builder.build();
}