I was recently implementing the
builder pattern for some immutable objects and was taking the approach described by Joshua Bloch in his excellent book '
Effective Java'. I particularly like the readability that the pattern provides when constructing complex objects and I think this arises because each property value is contextualized by the preceding method name:
Person.Builder builder = new Person.Builder();
Person person = builder.id(10).name("Jim").age(21)
.pets("cat", "dog").build();
I also like working with builders in an IDE because the auto-complete process can nicely lead you through the object's construction with a few taps of Ctrl+Space. However, there are a few things that could be improved with the pattern:
Handling of mandatory state
There are two obvious ways we can enforce mandatory properties with the pattern. Firstly we could pass mandatory property values in the builder constructor. This gives us a compile-time enforcement of mandatory properties. However, for many mandatory properties it can suffer from the problem that the pattern was meant to avoid - telescoping constructors. Imagine that the id and name are mandatory:
Person.Builder builder = new Person.Builder(1, "Jim");
Person person = builder.age(21).pets("cat", "dog").build();
The second way we can enforce mandatory properties is by checking the builder state in the build() method. This preserves the readablility API but relegates the mandatory property checking to run-time - quite a penalty.
Redundant invocation paths
Normally a property setter method on a builder just returns the builder instance:
public Builder id(int id) {
this.id = id;
return this;
}
This means that on the returned value we can call any other builder method, including the one that we just invoked. We could - if we wanted to - chain many redundant calls that overwrite the builder state:
Person person = builder.id(10).id(11).id(12).id(13).build();
While this isn't a likely to occur, it shows that the path we take through the building of the object isn't incredibly directed - it would be nice if we couldn't set the id more than once. We could do this at run-time but it would be far better if we were prevented from even writing such code! This lack of order also degrades our IDE auto-complete experience a little - imagine a builder with 20 properties - every time we see the auto-complete suggestions we have to mentally parse a list of 20 property setters - including those we've already set.
Adding order to the builder pattern
I had a go at addressing these (minor) short comings of the pattern and with some simple additions was able to create a builder that can lead you through the building process and enforce mandatory properties at compile-time in a nice contextualized way. The trick is to create a number of subordinate builder classes.
Consider an immutable value class:
public class Person {
private final int id; // MANDATORY
private final String name; // MANDATORY
private final int age; // OPTIONAL
private final Set pets; // OPTIONAL
...
Now let's deal with the mandatory fields. It makes sense that you must first set the id property before doing anything else. Therefore the only possible method invocation from our builder class will be id(int id) - but what should this return? The next step in our builder sequence is to set the mandatory name, therefore we should return a builder class on which you can only invoke the name(String name) method, so we defined a subordinate builder class NameRequiredFieldBuilder, that only allows the name to be set and return this from the invocation of id(int id):
public static class Builder {
...
public NameRequiredFieldBuilder id(int id) {
this.id = id;
return requiredName;
}
...
public static class NameRequiredFieldBuilder {
private final Builder builder;
private NameRequiredFieldBuilder(Builder builder) {
this.builder = builder;
}
public ? name(String name) {
builder.name = name;
return ?
}
So we can now change the builder invocations like so: builder.id(10).name("Jim") - in fact we no option but to call these methods, in this order. Let's deal with providing the builder with the the optional fields. At this point we'd like to be able to call age(int age), pets(String... pets), or build(). and we'd like to do this using the object returned by name(String name). To achieve this we define a subordinate builder class for the optional fields and the build() method:
public static class OptionalFieldsBuilder {
private final Builder builder;
private OptionalFieldsBuilder(Builder builder) {
this.builder = builder;
}
public OptionalFieldsBuilder age(int age) {
builder.age = age;
return builder.optionalFields;
}
public OptionalFieldsBuilder pets(String... pets) {
for (String pet : pets) {
builder.pets.add(pet);
}
return builder.optionalFields;
}
public Person build() {
return new Person(builder);
}
}
An instance of OptionalFieldsBuilder is then returned by NameRequiredFieldBuilder.name(String name):
public OptionalFieldsBuilder name(String name) {
builder.name = name;
return optionalFields;
}
So now we can call the builder property setters for the optional fields and then finally call the method to build an object instance.
Full source code for this example.
Some observations
Breaking the builder into a number of subordinate builder classes allows us to introduce order into the build process. We can lead users of our API through the construction process, having them specify mandatory fields values first, only once, and potentially in order of importance. The modified pattern ensures that all mandatory fields are supplied at compile-time as it's not possible to invoke build() otherwise. The modified pattern does away with constructor supplied parameters and potential telescoping issues. Furthermore the subordinate builder classes limit method choices in our IDE's auto-complete mechanism to only the pertinent properties.
We could introduce additional structure to the invocations of the setters of the optional properties, however this might lead to a proliferation of subordinate builder classes. I feel that there is no need to dictate the order in which optional properties are set, and indeed how many times they are set.
From an implementation perspective, the overhead of creating subordinate builder classes for a few mandatory properties is small. A more scalable approach would be to use an annotation processor to generate the builder classes at compile time as had been done with Jan-Kees van Andel's
make-builder tool.