![translation](https://cdn.durumis.com/common/trans.png)
This is an AI translated post.
[Effective Java] Item 2. Consider a Builder if Your Constructor Has Many Parameters
- Writing language: Korean
- •
-
Base country: All countries
- •
- Information Technology
Select Language
Summarized by durumis AI
- It introduces various patterns for efficiently managing constructors with many parameters, and compares and analyzes the advantages and disadvantages of the step-by-step constructor pattern, JavaBeans pattern, and builder pattern.
- The builder pattern has the advantage of increasing code readability and stability, and is even more effective when used with hierarchically designed classes.
- The builder pattern allows you to write code more clearly, but it has the drawbacks of builder object creation and code verbosity.
Telescoping Constructor Pattern
Both static factories and constructors have difficulty dealing with many parameters. For example, if a class has 6 fields and you want to divide the constructors when there are 2 parameters, 3 parameters, ... etc., you can use the telescoping constructor pattern as follows.
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, 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;
}
public NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0, 0, 0, 0);
}
public NutritionFacts(int servingSize, int servings, int calories) {
this(servingSize, servings, calories, 0, 0, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat) {
this(servingSize, servings, calories, fat, 0, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
this(servingSize, servings, calories, fat, sodium, 0);
}
However, even with this, if there are many parameters, it is confusing to read the code to understand the meaning of each value, and parameters of the same type can be confused and values can be inserted.
Java Beans Pattern
The Java Beans pattern creates an object with a constructor that takes no parameters, and then calls setter methods to set the values of the desired parameters.
public class NutritionFactsWithJavaBeansPattern {
private int servingSize = -1; // Required. No default value.
private int servings = -1; // Required. No default value.
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
public void setServingSize(int servingSize) {
this.servingSize = servingSize;
}
public void setServings(int servings) {
this.servings = servings;
}
public void setCalories(int calories) {
this.calories = calories;
}
public void setFat(int fat) {
this.fat = fat;
}
public void setSodium(int sodium) {
this.sodium = sodium;
}
public void setCarbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
}
The Java Beans pattern allows you to create an instance without confusing the values, even if there are many parameters. However, creating a single object requires multiple calls to setter methods, and consistency is broken until the object is fully complete. This makes it impossible to make the class immutable.
Builder Pattern
The builder pattern is mainly used, which combines the stability of the telescoping constructor pattern and the readability of the Java Beans pattern.
Instead of creating the object directly, the client calls the constructor with only the required parameters to obtain a builder object. Then, the builder object provides a kind of setter method to set the desired optional parameters. Finally, the build() method with no parameters is called to obtain the required object.
public class NutritionFactsWithBuilderPattern {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
private NutritionFactsWithBuilderPattern(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
public static class Builder {
private final int servingSize;
private final int servings;
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
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 sodium(int val) {
sodium = val;
return this;
}
public Builder carbohydrate(int val) {
calories = val;
return this;
}
public NutritionFactsWithBuilderPattern build() {
return new NutritionFactsWithBuilderPattern(this);
}
}
The constructor in the Builder class receives only the required parameters, and the remaining optional parameters are filled in with a kind of setter method. And finally, the build() method creates a complete NutritionFactsWithBuilderPattern object. The NutritionFactsWithBuilderPattern class is immutable, and the builder's setter methods return the builder itself, so they can be called in a chain. This method is called a fluent API or method chaining.
NutritionFactsWithBuilderPattern nutritionFacts =
new NutritionFactsWithBuilderPattern.Builder(240, 8)
.calories(100)
.sodium(35)
From the client's perspective, the builder pattern makes it easier to write and read code.
Builder Pattern Goes Well with Hierarchically Designed Classes
public abstract class Pizza {
public enum Topping {
HAM, MUSHROOM, ONION, PEPPER, SAUSAGE
}
final Set toppings;
Pizza(Builder> builder) {
toppings = builder.toppings.clone();
}
abstract static class Builder> {
private EnumSet toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(topping);
return self();
}
abstract Pizza build();
protected abstract T self();
}
The Pizza.Builder class is a generic type that uses recursive type bounds, and the self() abstract method is added to support method chaining without type casting in subclasses. Subclasses can simply give themselves the return value of this abstract method.
Now, let's look at the subclasses of Pizza, New York pizza and Calzone pizza, and experience the flexibility of the builder pattern.
public class NyPizza extends Pizza {
public enum Size {
SMALL, MEDIUM, LARGE
}
private final Size size; // Required parameter
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
public static class Builder extends Pizza.Builder {
private final Size size;
public Builder(Size size) {
this.size = size;
}
@Override
NyPizza build() {
return new NyPizza(this);
}
@Override
protected Builder self() {
return this;
}
}
}
public class CalzonePizza extends Pizza {
private final boolean sauceInside; // Optional parameter
private CalzonePizza(Builder builder) {
super(builder);
sauceInside = builder.sauceInside;
}
public static class Builder extends Pizza.Builder {
private boolean sauceInside = false;
public Builder sauceInside() {
sauceInside = true;
return this;
}
@Override
CalzonePizza build() {
return new CalzonePizza(this);
}
@Override
protected Builder self() {
return this;
}
}
The build() class defined by each subclass builder returns a specific subclass. The ability of a subclass method to return its subtype rather than the type returned by the superclass method is called covariant return typing. This allows clients to avoid type casting.
NyPizza nyPizza = new NyPizza.Builder(Size.SMALL)
.addTopping(Topping.SAUSAGE)
.addTopping(Topping.ONION)
.build();
CalzonePizza calzonePizza = new CalzonePizza.Builder()
.addTopping(Topping.HAM)
.sauceInside()
From the client's point of view, you can mix and match the Pizza's enum and each subclass's enum, and you can complete the object with each appropriate method.
Disadvantages of the Builder Pattern
- You have to create a builder object.
- Code is verbose.
Summary
Consider the builder pattern if the constructor or static factory method needs to handle many parameters.
Source
- Effective Java