The Builder pattern is extremely popular in Java applications. Unfortunately it's often misunderstood and incorrectly implemented and used which results to run time errors.
So, what to do in cases when object must be built completely and there are no safe defaults for fields (i.e. Builder pattern is not applicable)? In such cases would be very convenient to use Fluent Interface pattern which allows to avoid errors caused by missing field(s). Unfortunately proper implementation of Fluent Interface usually is too verbose to be practical so developers use plain Builder and try to rely on careful testing to avoid runtime errors.
Fortunately for most frequent use case when all fields for the object must be set, there is a simple solution which results to very concise and safe implementation of Builder with fluent interface, which I call Fluent Builder.
Lets assume that we want to create a Fluent Builder for simple bean shown below:
public class SimpleBean {
private final int index;
private final String name;
private SimpleBean(final int index, final String name) {
this.index = index;
this.name = name;
}
}
In Java 14 such classes can be declared as records so necessary boilerplate code will be significantly reduced.
Lets add a Builder. First step is quite traditional:
...
public static SimpleBeanBuilder builder() {
return new SimpleBeanBuilder();
}
...
Let's implement traditional builder first so it will be more clear how Fluent Builder code is derived. Traditional Builder class would look like this:
...
private static class SimpleBeanBuilder {
private int index;
private String name;
public SimpleBeanBuilder setIndex(final int index) {
this.index = index;
return this;
}
public SimpleBeanBuilder setName(final String name) {
this.name = name;
return this;
}
public SimpleBean build() {
return new SimpleBean(index, name);
}
}
One important observation: every setter returns this
and this in turn allows users of this call to invoke every method available in builder. This is the root of the issue, because user is allowed to call build()
method prematurely, before all necessary fields are set.
In order to make Fluent Builder we need to limit possible choices to only allowed ones, therefore enforcing correct use of the builder. Since we're considering case when all fields need to be set, then at every building step only one method is available. To do this we can return dedicated interfaces instead of this
and let Builder implement all these interfaces:
...
public static SimpleBeanBuilder0 builder() {
return new SimpleBeanBuilder();
}
...
private static class SimpleBeanBuilder implements SimpleBeanBuilder0, SimpleBeanBuilder1, SimpleBeanBuilder2 {
private int index;
private String name;
public SimpleBeanBuilder1 setIndex(final int index) {
this.index = index;
return this;
}
public SimpleBeanBuilder2 setName(final String name) {
this.name = name;
return this;
}
public SimpleBean build() {
return new SimpleBean(index, name);
}
public interface SimpleBeanBuilder0 {
SimpleBeanBuilder1 setIndex(final int index);
}
public interface SimpleBeanBuilder1 {
SimpleBeanBuilder2 setName(final String name);
}
public interface SimpleBeanBuilder2 {
SimpleBean build();
}
}
Huh. Somewhat ugly and a lot of additional boilerplate. Can we do better? Let's try.
First step is to stop implementing interfaces and instead return anonymous classes which implement these interfaces:
public static SimpleBeanBuilder builder() {
return new SimpleBeanBuilder();
}
...
...
private static class SimpleBeanBuilder {
public SimpleBeanBuilder1 setIndex(int index) {
return new SimpleBeanBuilder1() {
@Override
public SimpleBeanBuilder2 setName(final String name) {
return new SimpleBeanBuilder2() {
@Override
public SimpleBean build() {
return new SimpleBean(index, name);
}
};
}
};
}
public interface SimpleBeanBuilder1 {
SimpleBeanBuilder2 setName(final String name);
}
public interface SimpleBeanBuilder2 {
SimpleBean build();
}
}
This is much better. We again can safely return SimpleBeanBuilder from builder()
method since this class exposes only one method and does not allow to build instance prematurely. But much more important is that we can omit whole setters and mutable fields boilerplate in the builder significantly reducing amount of code. This is possible because we creating anonymous classes in the scope where parameters of all setters are visible and accessible. And we can use these parameters directly without need to store them!
Resulting code looks is comparable to original Builder implementation in regard to total amount of code.
But we can do better! Since all anonymous classes are in fact implementation of the interfaces which contain only one method, we can replace anonymous classes with lambdas:
private static class SimpleBeanBuilder {
public SimpleBeanBuilder1 setIndex(int index) {
return name -> () -> new SimpleBean(index, name);
}
public interface SimpleBeanBuilder1 {
SimpleBeanBuilder2 setName(final String name);
}
public interface SimpleBeanBuilder2 {
SimpleBean build();
}
}
Resulting Fluent Builder contains even less boilerplate code than original builder!
But we can do even better! Notice that remaining SimpleBeanBuilder
class is very similar to other builder interfaces, so we can replace it with lambda as well:
public static SimpleBeanBuilder builder() {
return index -> name -> () -> new SimpleBean(index, name);
}
public interface SimpleBeanBuilder {
SimpleBeanBuilder1 setIndex(int index);
}
public interface SimpleBeanBuilder1 {
SimpleBeanBuilder2 setName(final String name);
}
public interface SimpleBeanBuilder2 {
SimpleBean build();
}
For those who isn't yet used to deeply nested lambdas this code might be harder to read. From the other hand, there is no need to write it manually as we can offload this task to IDE (just as we do with traditional builders).
Using this approach we can replace traditional Builders with Fluent Builders and get Builder convenience with Fluent Interface safety.