Static factories and constructors share a limitation – they do not scale well to large number of parameters. Consider a class representing the Nutrition facts label that appears on packaged food. These labels have a few required fields – serving size, servings per container, and calories per serving – and over twenty optional fields. Most products have nonzero values for only a few of these optional fields. Traditionally programmers have used the telescoping constructor pattern, in which you provide a constructor with the only required parameters, and so on, culminating in a constructor with all the optional parameters.
When you want to create an instance, you use the constructor with the shortest parameter list containing all parameters that you want to set:
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
Typically this constructor invocation will require many parameters that you don’t want to set, but you’re force to pass a value for them anyway.
Another approach for solving this problem would be JavaBeans in which you call parameterless constructor and then call setter methods.
//JavaBeans Pattern - allows inconsistency, mandates mutability public class NutritionFacts { //Parameters initialized to default values (if any) ... public NistritionFacts() { } //Setters public void setServingSize(int val) { servingSize = val; } public void setServings(int val) { servings = val; } ... }
This pattern has none of the disadvantages of the telescoping constructor pattern. It is easy to create instances, and easy to read the resulting code:
NutritionFacts cocaCola = new NutritionFacts(); cocaCola.setServingSize(240); cocaCola.setServings(8); ....
Unfortunately JavaBeans pattern has serious disadvantages of its own, a JavaBean may be in an inconsistent state partway through its constructor.
Builder Pattern
As we have learned in school, test should be added first (TDD):public class NutritionFactsTest { @Test public void builderDemo() { NutritionFacts nutritionFacts = new NutritionFacts.Builder(240) .withServings(8) .withCalories(100) .build(); assertEquals(100, nutritionFacts.getCalories()); assertEquals(8, nutritionFacts.getServings()); assertEquals(240, nutritionFacts.getServingSize()); } }
public class NutritionFacts { private final int servingSize; private final int servings; private final int calories; private NutritionFacts(Builder builder) { this.servingSize = builder.servingSize; this.servings = builder.servings; this.calories = builder.calories; } public int getServingSize() { return servingSize; } public int getServings() { return servings; } public int getCalories() { return calories; } public static class Builder { //Required parameters private int servingSize; //Optional parameters - initialized to default values private int servings = 0; private int calories = 0; public Builder(int servingSize) { this.servingSize = servingSize; } public Builder withServings(int servings) { this.servings = servings; return this; } public Builder withCalories(int calories) { this.calories = calories; return this; } public NutritionFacts build() { return new NutritionFacts(this); } } }
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 solutions. Also, Builder pattern is more verbose than the telescoping constructor pattern, so it should be used only if there are enough parameters. On the other hand, you may want to add parameters in the future, and in case you decide to add a Builder at that moment, there will be more work to do.
It’s often better to start with a builder in the first place.
In summary, the Builder pattern is a good choice when designing classes whose constructor or static factories would have more than a handful of parameters, especially if most of those parameters are optional.