Builder Design Pattern
Builder is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.
Problem
Imagine you have an item that is quite complicated to create and has many possible combinations. For example, it could be a car. Some cars will be in the basic version, while others may have many additional things.
You might think about creating a class with common fields that others will extend as they have additional functions.
This solution comes with another problem, which is many classes extending the Car class, e.g. CarWithAutopilot, CarWithSteeringWheel, CarInSportVersion. So it is not the optimal solution.
Another idea might be to create just one class, just Car, which will have all the possibilities as fields such as hasAutopilot, isSportVersion, hasSteeringWheel, additionalWarranty, soundSystem, and so on. So if we create a base car, in most constructor fields we will pass null, which looks messy.
Solution
The Builder design pattern suggests extracting the construction code from its class and placing it in separate objects called builders.
In the classic Gang of Four (GoF) formulation, a Director class orchestrates the building process. The Director defines the order in which to call construction steps, while the Builder provides the implementation for those steps. This separation lets you reuse the same building routine across different builders.
In practice, especially in Java, I reach for the fluent builder variant far more often. Here the builder returns this from each setter method, enabling a readable method chain without a separate Director class.
1
2
3
4
5
Car car = new Car.Builder()
.withEngine("V8")
.withAutopilot(true)
.withSoundSystem("Bose")
.build();
The fluent builder is so common that Lombok can generate the entire boilerplate for you with a single @Builder annotation:
1
2
3
4
5
6
7
8
9
10
11
12
13
@Builder
public class Car {
private final String engine;
private final boolean autopilot;
private final String soundSystem;
}
// usage
Car car = Car.builder()
.engine("V8")
.autopilot(true)
.soundSystem("Bose")
.build();
Under the hood Lombok generates the static inner Builder class, fluent setters, and the build() method. I use this in nearly every project where I need builders – it removes a significant amount of repetitive code.
Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
public class Car {
//final attributes
private final CarType carType; // required
private final Engine engine; // required
private final Transmission transmission;// required
private final int seats; // required
private final int additionalWarranty; // optional
private final String soundSystem; // optional
private final boolean hasHeatedSeats; // optional
private final boolean hasHeatedWheel; // optional
public Car(Builder builder) {
this.carType = builder.carType;
this.engine = builder.engine;
this.transmission = builder.transmission;
this.seats = builder.seats;
this.additionalWarranty = builder.additionalWarranty;
this.soundSystem = builder.soundSystem;
this.hasHeatedSeats = builder.hasHeatedSeats;
this.hasHeatedWheel = builder.hasHeatedWheel;
}
//All getter, and NO setter to preserve immutability
public CarType getCarType() {
return carType;
}
public Engine getEngine() {
return engine;
}
public Transmission getTransmission() {
return transmission;
}
public int getSeats() {
return seats;
}
public int getAdditionalWarranty() {
return additionalWarranty;
}
public String getSoundSystem() {
return soundSystem;
}
public boolean isHasHeatedSeats() {
return hasHeatedSeats;
}
public boolean isHasHeatedWheel() {
return hasHeatedWheel;
}
public static class Builder {
private final CarType carType;
private final Engine engine;
private final Transmission transmission;
private final int seats;
private int additionalWarranty;
private String soundSystem;
private boolean hasHeatedSeats;
private boolean hasHeatedWheel;
public Builder(CarType carType,
Engine engine,
Transmission transmission,
int seats) {
this.carType = carType;
this.engine = engine;
this.transmission = transmission;
this.seats = seats;
}
public Builder additionalWarranty(int additionalWarranty) {
this.additionalWarranty = additionalWarranty;
return this;
}
public Builder soundSystem(String soundSystem) {
this.soundSystem = soundSystem;
return this;
}
public Builder hasHeatedSeats(boolean hasHeatedSeats) {
this.hasHeatedSeats = hasHeatedSeats;
return this;
}
public Builder hasHeatedWheel(boolean hasHeatedWheel) {
this.hasHeatedWheel = hasHeatedWheel;
return this;
}
//Return the finally constructed Car object
public Car build() {
Car car = new Car(this);
validateCarObject(car);
return car;
}
private void validateCarObject(Car car) {
//Do some basic validations to check
//if Car object does not break any assumption of system
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
Car car1 = new Car.Builder(SPORTS_CAR, new Engine( volume: 6.0, mileage: 0), AUTOMATIC, seats: 2)
.hasHeatedSeats(true)
.hasHeatedWheel(false)
.additionalWarranty(3)
.soundSystem("Sony")
.build();
Car car2 = new Car.Builder(CITY_CAR, new Engine( volume: 1.5, mileage: 0), AUTOMATIC, seats: 5)
.hasHeatedSeats(true)
.hasHeatedWheel(true)
//no additional warranty
//no sound system
.build();
}
Use the Builder pattern to get rid of a “telescopic constructor”.
Use the Builder pattern when you want your code to be able to create different representations of some product (for example, stone and wooden houses).
Use the Builder to construct Composite trees or other complex objects.
Use the Builder when you want to facilitate creating immutable objects. The builder accumulates state through mutable setters, then the
build()method produces a fully initialized object with all fields set tofinal. The builder itself does not make your code immutable – it gives you a clean way to construct objects that are.
Pros and Cons
| Pros | Cons |
|---|---|
| You can construct objects step-by-step, defer construction steps or run steps recursively. | The overall complexity of the code increases since the pattern requires creating multiple new classes. |
| You can reuse the same construction code when building various representations of products. | If the object has only a few fields, a builder adds unnecessary indirection -- a simple constructor or static factory method may be a better fit. |
| Facilitates creating immutable objects by centralizing construction logic while keeping the resulting object's fields final. | |
| Single Responsibility Principle. You can isolate complex construction code from the business logic of the product. |