Using the Builder pattern when we encounter a constructor with many parameters
This article presents a free translation of a chapter from Effective Java, Second Edition by Joshua Bloch.
The article discusses 3 alternative approaches to simplify the use of the class, with a constructor with many parameters.
Consider the case when we have a class that provides an inscription on the packaging of food products. This inscription is responsible for the chemical composition of the product. This label has several required fields: serving size, number of servings in a container, calorie content of one serving and about twenty additional fields: total fats, saturated fats, trans fats, amount of cholesterol, amount of sodium, and so on. Most products have non-zero values for only a few of these additional fields.
Traditionally, programmers have used the Telescoping Constructor pattern. The essence of this pattern is that you provide several constructors: a constructor with required parameters, a constructor with one additional parameter, a constructor with two additional parameters, and so on. Demonstrate how it will look in practice. For brevity, we will use only 4 additional parameters.
When you want to create an object of this class, you use the constructor with the necessary list of parameters:
Usually, to call the constructor, you will need to pass a lot of parameters that you do not want to set, but in any case you are forced to pass a value for them. In our case, we set the value to 0 for the field
In short, using the Telescoping Constructor pattern, it becomes difficult to write client code when there are many parameters, and even more difficult to read this code. The reader can only guess what all these values mean and you need to carefully calculate the position of the parameter in order to find out which field it refers to. Long sequences of identically typed parameters can cause subtle errors. If the client accidentally mixes up two of these parameters, then the compilation will be successful, but the program will not work correctly.
The second option, when you come across a constructor with many parameters, is a JavaBeans pattern. You call the constructor without parameters to create the object, and then you call the setters to set the required and additional parameters of interest:
This approach has no drawbacks of the Telescoping Constructor pattern (the object is easy to create and the resulting code is easy to read):
Unfortunately, the JavaBeans pattern is not without serious flaws. Because construction is split between multiple calls, a JavaBean may be in an unstable state, having partially gone through construction. Attempting to use an object if it is in an unstable state can lead to errors that are far from the code containing the error, and therefore difficult to debug. Also, JavaBeans pattern eliminates the possibility of making the class immutable, which requires additional efforts on the part of the programmer to ensure security in a multi-threaded environment.
Fortunately, there is a third alternative that combines the security of the Telescoping Constructor pattern with the readability of the JavaBeans pattern. It is a form of the Builder pattern . Instead of directly creating the desired object, the client calls the constructor (or static factory) with all the necessary parameters and receives the builder object. The client then calls setter-like methods on the builder object to set each additional parameter. Finally, the client calls a method
Please note that NutritionFacts is immutable and that all default parameter values are in one place. The builder setter methods are returned by this builder. Therefore, calls can be chained together. This is what the client code looks like:
This client code is easy to write and, more importantly, easy to read. The Builder pattern mimics the optional name parameters that are used in Ada and Python.
UPDATE
According to the commentary it is suggested:
for the considered NutritionFacts class it is logical to provide getters for immutable fields. Otherwise, it turns out that we will build the object, but we won’t be able to use it.
It’s more logical to name Builder’s fields in accordance with the JavaBeans convention, namely setXXX (). Since this method is already the de facto standard for Java, such an approach will improve code readability.
ps My first publication on a habr Do not kick much;)
The article discusses 3 alternative approaches to simplify the use of the class, with a constructor with many parameters.
Consider the case when we have a class that provides an inscription on the packaging of food products. This inscription is responsible for the chemical composition of the product. This label has several required fields: serving size, number of servings in a container, calorie content of one serving and about twenty additional fields: total fats, saturated fats, trans fats, amount of cholesterol, amount of sodium, and so on. Most products have non-zero values for only a few of these additional fields.
First Alternative (Telescoping Constructor Pattern)
Traditionally, programmers have used the Telescoping Constructor pattern. The essence of this pattern is that you provide several constructors: a constructor with required parameters, a constructor with one additional parameter, a constructor with two additional parameters, and so on. Demonstrate how it will look in practice. For brevity, we will use only 4 additional parameters.
// Telescoping constructor 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 NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0);
}
public NutritionFacts(int servingSize, int servings, int calories) {
this(servingSize, servings, calories, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat) {
this(servingSize, servings, calories, fat, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat,
int sodium) {
this(servingSize, servings, calories, fat, sodium, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat,
int sodium, int carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
}
When you want to create an object of this class, you use the constructor with the necessary list of parameters:
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
Usually, to call the constructor, you will need to pass a lot of parameters that you do not want to set, but in any case you are forced to pass a value for them. In our case, we set the value to 0 for the field
fat. Since we have only six parameters, it may seem that this is not so bad. But this begins to cause huge problems when the number of parameters increases.In short, using the Telescoping Constructor pattern, it becomes difficult to write client code when there are many parameters, and even more difficult to read this code. The reader can only guess what all these values mean and you need to carefully calculate the position of the parameter in order to find out which field it refers to. Long sequences of identically typed parameters can cause subtle errors. If the client accidentally mixes up two of these parameters, then the compilation will be successful, but the program will not work correctly.
Second Alternative (JavaBeans Pattern)
The second option, when you come across a constructor with many parameters, is a JavaBeans pattern. You call the constructor without parameters to create the object, and then you call the setters to set the required and additional parameters of interest:
// JavaBeans Pattern - allows inconsistency, mandates mutability
public class NutritionFacts {
// Параметры инициализируются значениями по умолчанию
private int servingSize = -1; // Обязательный
private int servings = -1; // Обязательный
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public NutritionFacts() {
}
// Сеттеры
public void setServingSize(int val) {
servingSize = val;
}
public void setServings(int val) {
servings = val;
}
public void setCalories(int val) {
calories = val;
}
public void setFat(int val) {
fat = val;
}
public void setSodium(int val) {
sodium = val;
}
public void setCarbohydrate(int val) {
carbohydrate = val;
}
}
This approach has no drawbacks of the Telescoping Constructor pattern (the object is easy to create and the resulting code is easy to read):
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
Unfortunately, the JavaBeans pattern is not without serious flaws. Because construction is split between multiple calls, a JavaBean may be in an unstable state, having partially gone through construction. Attempting to use an object if it is in an unstable state can lead to errors that are far from the code containing the error, and therefore difficult to debug. Also, JavaBeans pattern eliminates the possibility of making the class immutable, which requires additional efforts on the part of the programmer to ensure security in a multi-threaded environment.
Third Alternative (Builder Pattern)
Fortunately, there is a third alternative that combines the security of the Telescoping Constructor pattern with the readability of the JavaBeans pattern. It is a form of the Builder pattern . Instead of directly creating the desired object, the client calls the constructor (or static factory) with all the necessary parameters and receives the builder object. The client then calls setter-like methods on the builder object to set each additional parameter. Finally, the client calls a method
build()to generate an object that will be immutable. A builder is a static inner class in the class that he is building. Here's what it looks like in practice:// паттерн Builder
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 {
// Обязательные параметры
private final int servingSize;
private final int servings;
// Дополнительные параметры - инициализируются значениями по умолчанию
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;
}
}
Please note that NutritionFacts is immutable and that all default parameter values are in one place. The builder setter methods are returned by this builder. Therefore, calls can be chained together. This is what the client code looks like:
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();
This client code is easy to write and, more importantly, easy to read. The Builder pattern mimics the optional name parameters that are used in Ada and Python.
UPDATE
According to the commentary it is suggested:
for the considered NutritionFacts class it is logical to provide getters for immutable fields. Otherwise, it turns out that we will build the object, but we won’t be able to use it.
It’s more logical to name Builder’s fields in accordance with the JavaBeans convention, namely setXXX (). Since this method is already the de facto standard for Java, such an approach will improve code readability.
ps My first publication on a habr Do not kick much;)