⬆️ ⬇️

Using the Builder pattern when we encounter a designer with many parameters

This article presents a free translation of a chapter from the book Effective Java, Second Edition by Joshua Bloch.



The article discusses 3 alternative approaches to simplifying the use of a class, with a constructor with many parameters.





Consider the case when we have a class that provides an inscription on the packaging of food. This inscription is responsible for the chemical composition of the product. This inscription has several required fields: the serving size, the number of servings per container, the caloric content of one serving and about twenty additional fields: total fats, saturated fats, trans fats, the amount of cholesterol, the amount of sodium, and so on. Most products have nonzero values ​​for only a few of these additional fields.

')



First Alternative (Telescoping Constructor Pattern)





Traditionally, programmers 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. Let us 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 a constructor with the necessary list of parameters:





NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);







Usually, to call a constructor, you will need to pass many 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 fat field. Since we only have six parameters, it may not seem so bad. But it starts to deliver 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 harder 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 to find out which field it belongs to. Long sequences of equally typed parameters can cause subtle errors. If the client accidentally confuses two of these parameters, the compilation will be successful, but the program will not work correctly.





Second Alternative (JavaBeans Pattern)





The second option, when you encounter a multi-parameter constructor, is the JavaBeans pattern. You call a parameterless constructor to create an object, and then call setters to set the required and optional 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 does not have the disadvantages of a 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 divided between multiple calls, a JavaBean may be in an unstable state partially passing 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, hence difficult to debug. Also, the JavaBeans pattern eliminates the ability to make a class immutable, which requires additional effort from 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 a static factory) with all the necessary parameters and gets the builder object. The client then calls the setter-like methods on the builder object to set each additional parameter. Finally, the client calls the build() method to generate an object that will be immutable. The builder is a static inner class in the class that he builds. Here is how it looks 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;

}

}






Note that NutritionFacts is immutable, and that all default parameter values ​​are in the same place. The builder's setter methods return this builder himself. Therefore, calls can be chained. 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 Pattern Builder simulates the named extra parameters that are used in Ada and Python.





UPDATE

According to the commentary it is proposed:

for the considered NutritionFacts class, it is logical to provide getters for immutable fields. Otherwise, it turns out that we will construct the object, but we cannot use it.

It is more logical to call the Builder's fields in accordance with the JavaBeans convention, namely setXXX (). Since this method is already the de facto standard for Java, this approach will improve the readability of the code.



ps My first post on Habré. Do not kick much;)



Source: https://habr.com/ru/post/86252/



All Articles