Static
factories and constructors share a limitation: they do not scale well to large numbers
of optional parameters.
Consider
Nutrition Facts label that appears on packaged foods.
required
fields—serving size, servings per container, and calories per serving— and over
twenty
optional
fields—total fat, saturated fat, trans fat, cholesterol, sodium, and so on.
1)
Traditionally, programmers have used the telescoping constructor
pattern, in which you provide a constructor with only the required parameters,
another with a single optional parameter, a third with two optional parameters,
and so on.
Telescoping constructor pattern - does not scale
well!
public class NutritionFacts {
private final int servingSize; // (mL) required
private final int servings; // (per container) required
private final int calories; // optional
private final int fat; // (g) optional
private final int sodium; // (mg) optional
private final int carbohydrate; // (g) optional
public NutritionFacts(int servingSize, int servings) {
//you know what is this }
public NutritionFacts(int servingSize, int servings, int
calories) {//you know what is this }
public NutritionFacts(int servingSize, int servings, int
calories, int fat) {//you know what is this }
public NutritionFacts(int servingSize, int servings, int
calories, int fat, int sodium) {//you know what is this }
public NutritionFacts(int servingSize, int servings, int
calories, int fat, int sodium, int carbohydrate) {//you know what is this }
}
When you want
to create an instance, you use the constructor with the shortest parameter list
containing all the parameters you want to set:
NutritionFacts cocaCola =
new NutritionFacts(240, 8, 100, 0, 35, 27);
the telescoping
constructor pattern works, but it is hard to write
client code when
there are many parameters, and harder still to read it.
If the client
accidentally reverses two such parameters, the compiler won’t complain, but the
program will misbehave at runtime (Item 40).
2)
the JavaBeans pattern, in
which you call a parameterless constructor to create the
object and
then call setter methods to set each required parameter and each
optional
parameter of interest
JavaBeans Pattern - allows inconsistency, mandates
mutability
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
This pattern
has none of the disadvantages of the telescoping constructor pattern. It is
easy, if a bit wordy, to create instances, and easy to read the resulting code.
Unfortunately,
the JavaBeans pattern has serious disadvantages of its own. Because construction
is split across multiple calls, a JavaBean may be in an inconsistent state
partway through its construction.
A related
disadvantage is that the JavaBeans pattern precludes the possibility of making a class
immutable (Item 15), and requires added effort on the
part of the programmer to ensure thread safety.
3)
Builder pattern
Combines the
safety of the telescoping constructor pattern with the readability of the
JavaBeans pattern. Instead of making the desired object directly, the client
calls a constructor (or static factory) with all of the required parameters and
gets a builder object. Then the client calls setter-like methods
on the builder object to set each optional parameter of interest. Finally, the
client calls a parameterless build method to
generate the object, which is immutable. The builder is a static member class (Item 22) of the class it builds.
// Builder Pattern
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static class Builder {
// Required parameters
private final int servingSize;
private final int servings;
// Optional parameters - initialized to default values
private int calories = 0;
private int fat = 0;
private int carbohydrate = 0;
private int sodium = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val)
{ calories = val; return this; }
public Builder fat(int val)
{ fat = val; return this; }
public Builder carbohydrate(int val)
{ carbohydrate = val; return this; }
public Builder sodium(int val)
{ sodium = val; return this; }
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
Here’s how the
client code looks:
NutritionFacts cocaCola = new
NutritionFacts.Builder(240, 8).
calories(100).sodium(35).carbohydrate(27).build();
The Builder
pattern simulates named optional parameters.
A minor advantage of builders over constructors
is that builders can have multiple varargs parameters.
The Builder
pattern does have disadvantages of
its own. In order to create an object, you must first create its builder. While
the cost of creating the builder is unlikely to be noticeable in practice, it
could be a problem in some performance critical situations.
In summary, the Builder
pattern is a good choice when designing classes whose constructors or static
factories would have more than a handful of parameters, especially
if most of those parameters are optional. Client code is much easier to read
and write with builders than with the traditional telescoping constructor
pattern, and builders are much safer than JavaBeans.
Reference: Effective Java 2nd Edition by Joshua Bloch