The elegance of Builder pattern
Paraphrasing Josh Bloch in Effective Java [Bloch, 2017, Item 2]:
While creating objects, in cases where the number of optional parameters of an object is considerable, say 4 or more, one might think of static factory methods [Bloch, 2017, Item 1] as a solution -- but they're more suitable for a small set of parameters. When there are several optional params, static factories cannot be used as it's cumbersome to imagine and cater to all possible parameter combinations. Another approach that's proposed in such cases is using JavaBeans but it has its own shortcomings.
Therefore, we usually go with multiple (telescoping) constructors for such requirements. For example:
public Cake(int oilTbsp, int flourMg){
this(oilTbsp, flourMg, 0);
}
public Cake(int oilTbsp, int flourMg, int eggCount){ this(oilTbsp, flourMg, eggCount, 0); }
public Cake(int oilTbsp, int flourMg, int eggCount, int bakingPowderMg){ this(oilTbsp, flourMg, eggCount, bakingPowederMg); }
//...
Such implementations, although purpose-serving, are a bit contrived in that the class client needs to tally the parameters accurately. Consider a large parameter list, and this would be an overkill.
A variation of Builder pattern [Gamma, 1995], is what Bloch suggests, for such cases. In it, a builder class is a static member of the class it builds, for example:
public class Cake{
//...
private Cake(Builder builder){
//...
}
public static class Builder{ //... } } Since the original constructor is hidden, the client first gets a reference to the Builder class -- passing all the required params to its constructor or static factory. The client then calls setters on the returned builder object to set the optional parameters of interest. Finally, the client makes a call to the build()
method to generate an immutable object. Since the builder setter methods return the builder itself, the invocations can be chained, like so:
// Set only the parameters of interest
Cake cake = new Cake.Builder(350, 45).egg(2).sugar(240).cocoa(35)...build();
As is apparent, this is intuitive as well as concise.
A builder can be further enhanced by enabling it to build more than one object, based on parameters. One has to be cautious, however, to disallow building an object of an inconsistent state. This can be ensured by validating the passed parameters as early as possible and throwing a suitable exception.
Builders can also be used to automate certain tasks and fill in the fields. For example, autoincrementing the object Id, etc.
As Josh Bloch advises, we should be using Builders as often as possible, especially in cases where the number of parameters is significant. They're a simple and elegant alternative to telescoping constructors or JavaBeans.
[Full implementation of the Cake builder example is here.]