Disclaimer: This article is only for personal learning and communication, do not be used for commercial purposes, if this article is used for commercial purposes, the consequences are at your own risk, and I have nothing to do with it.

Static factories and constructors have a common limitation: neither scale well to a large number of optional parameters. Consider a class that represents the nutrition facts label displayed on the outside of a packaged food. Several fields are required on these labels: serving size, serving size per can and calories per serving, plus more than 20 optional fields: total fat, saturated fat amount, trans fat, cholesterol, sodium, and more. Most products have non-zero values in one of several optional fields.

Which constructor or static method should you write for such a class? Programmers have traditionally followed the overlapping constructor pattern, in which a constructor is provided with only necessary parameters, a second constructor with one optional parameter, a third with two optional parameters, and so on, and the last constructor with all optional parameters. Here is an example that shows four optional fields for simplicity:

// 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; // (per serving) optional
    private final int fat; // (g/serving) optional
    private final int sodium; // (mg/serving) optional
    private final int carbohydrate; // (g/serving) optional

    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; }}Copy the code

NutritionFacts cocaCola = New NutritionFacts(240, 8, 100, 0, 35, 27); NutritionFacts cocaCola = New NutritionFacts(240, 8, 100, 0, 35, 27); This constructor call usually requires a lot of parameters that you don’t want to set, but you still have to pass values for them. In this example, we pass a value of 0 to fat. If “just” were these six parameters, it wouldn’t look too bad, but as the number of parameters increases, it quickly gets out of control.

In general, it is possible to use the overlapping constructor pattern, but it is difficult to write client-side code and read them when there are many parameters. If the reader wants to know what these values mean, he must count the parameters carefully. A long list of arguments of the same type can cause subtle errors. If the client accidentally reverses the order of two intermediate arguments, the compiler will not give an error, but the program will behave incorrectly when it is run.

There is a second alternative when encountering many constructor arguments, the JavaBean pattern, in which a no-parameter constructor is called to create an object and setter methods are called to set each required argument, along with each associated optional argument:

// JavaBeans Pattern - allows inconsistency, mandates mutability
public class NutritionFacts {
    // Parameters initialized to default values (if any)
    private int servingSize = -1; // Required; no default value
    private int servings = -1; // Required; no default value
    private int calories = 0;
    private int fat = 0;
    private int sodium = 0;
    private int carbohydrate = 0;
    public NutritionFacts(a) {}// Setters
    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; }}Copy the code

This pattern makes up for the deficiency of the overlapping constructor pattern. To put it bluntly, it’s easy to create instances, 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);
Copy the code

Unfortunately, this JavaBean pattern has serious drawbacks of its own. Because the construction process is split into several calls, javabeans may be in an inconsistent state during construction. Class cannot guarantee consistency by checking the validity of constructor arguments. Trying to use an object in an inconsistent state will result in a failure that is very different from the code that contains the error, making it difficult to debug. A related shortcoming is that the JavaBean pattern prevents the class from being made immutable (item 17), which requires the programmer to make extra effort to keep it thread-safe.

Locking the object before the constructor completes construction and unlocking it after construction can compensate for this, but it is clumsy and rarely used in practice. In addition, it can even fail at runtime because the compiler cannot be sure that the program will lock before using the constructor.

Fortunately, there is a third alternative that combines the security of the overlapping constructor with the readability of the JavaBean schema. This is a form of Builder mode [Gamma95]. Instead of generating the desired object directly, have the client call a constructor method (or static factory method) with all the necessary parameters to get a Builder object, and then call setter-like methods on the Builder object to set each associated optional parameter. Finally, the client calls the build method with no arguments to generate the immutable object. The Builder is usually a static member class of the class it builds (item 24). Here is an example of it:

// 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 sodium = 0;
        private int carbohydrate = 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 sodium(int val) {
            sodium = val;
            return this;
        }

        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }

        public NutritionFacts build(a) {
            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; }}Copy the code

NutritionFacts is immutable and all default parameter values are placed in a separate place. Setter methods on the Builder return the Builder itself so that calls can be concatenated. Here is the client code:

NutritionFacts cocaCola = new NutritionFacts.Builder(240.8).calories(100).sodium(35).carbohydrate(27).build();
Copy the code

Such client code is easy to write and, more importantly, easy to read. The Builder pattern mimics the naming optional parameter in Python and Scala.

Validity checking has been omitted for brevity. To detect invalid parameters as quickly as possible, check parameter validity in the constructor’s constructors and methods. Check for invariants that involve more than one parameter in the constructor of the build method call. To ensure that these invariants are not vulnerable, perform a check on the object fields after copying the parameters from the builder (item 50). If the check fails, IllegalArgumentException(item 72) is thrown, with a detailed message indicating which arguments are invalid (item 75).

The Builder pattern is perfect for class hierarchies. Use a parallel hierarchy of builders, each nested in a corresponding class. Abstract classes have abstract builders; Specific courses include concrete builders. For example, consider using an abstract class at the root of a hierarchy representing various pizzas:

// Builder pattern for class hierarchies
public abstract class Pizza {
    public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
    final Set<Topping> toppings;
    abstract static class Builder<T extends Builder<T>> {
        EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);

        public T addTopping(Topping topping) {
            toppings.add(Objects.requireNonNull(topping));
            return self();
        }

        abstract Pizza build(a);
        // Subclasses must override this method to return "this"
        protected abstract T self(a); } Pizza(Builder<? > builder) { toppings = builder.toppings.clone();// See Item 50}}Copy the code

Notice the Pizza class, Builder is a generic type with recursive type parameters (item 30). This, along with the abstract method self, allows method chains to work properly in subclasses without casting. This workaround for the fact that Java lacks a self type is known as the simulated self type idiom The self – type the idiom.).

These are two concrete subcategories of Pizza, one of which stands for standard New York-style Pizza and the other for Calzone. The former has the required size parameters, while the latter allows you to specify whether the sauce should be inside or outside:

public class NyPizza extends Pizza {
    public enum Size { SMALL, MEDIUM, LARGE }
    private final Size size;
    public static class Builder extends Pizza.Builder<Builder> {
        private final Size size;

        public Builder(Size size) {
            this.size = Objects.requireNonNull(size);
        }

        @Override public NyPizza build(a) {
            return new NyPizza(this);
        }

        @Override protected Builder self(a) {
            return this; }}private NyPizza(Builder builder) {
        super(builder); size = builder.size; }}public class Calzone extends Pizza {
    private final boolean sauceInside;

    public static class Builder extends Pizza.Builder<Builder> {
        private boolean sauceInside = false; // Default
        public Builder sauceInside(a) {
            sauceInside = true;
            return this;
        }

        @Override public Calzone build(a) {
            return new Calzone(this);
        }

        @Override protected Builder self(a) {
            return this; }}private Calzone(Builder builder) {
        super(builder); sauceInside = builder.sauceInside; }}Copy the code

Note that the build method in the Builder of each subclass is declared to return the correct subclass: the build method in NyPizza.Builder returns NyPizza, while the build method in calzone. Builder returns Calzone. This technique, where a subclass method declares a return type that is a subtype of the return type declared in the superclass, is called a covariant return type and allows customers to use these builders without having to create them.

The client code for these “layered builders” is essentially the same as the code for the simple NutritionFacts builder. For brevity, the sample client code shown below assumes static imports on enumeration constants:

NyPizza pizza = new NyPizza.Builder(SMALL).addTopping(SAUSAGE).addTopping(ONION).build();
Calzone calzone = new Calzone.Builder().addTopping(HAM).sauceInside().build();
Copy the code

A small advantage of a builder over a constructor is that the builder can have multiple mutable parameters, because each parameter is specified in its own method. In addition, the builder can aggregate multiple parameters passed to a method into a single field by calling the method multiple times, as demonstrated earlier in the addTopping method.

The Builder mode is very flexible. You can reuse a single builder to build multiple objects. Builder parameters can be adjusted between calls to build methods to change the objects created. The builder can automatically populate certain fields when an object is created, such as the serial number that is added each time the object is created.

The Builder model also has drawbacks. To create an object, you must first create its builder. While the cost of creating this builder is unlikely to be obvious in practice, problems can arise in performance critical situations. In addition, the Builder pattern is more verbose than the overlapping constructor pattern, so it is only worthwhile to use it if you have enough parameters (for example, four or more). But keep in mind that you may want to add more parameters in the future. But if you start with constructors or static factories and switch to builders when the class evolves to a point where the number of arguments gets out of control, those outdated constructors and static factories can look very incongruous. Therefore, it is best to consider using constructors in the first place.

In short, if a class has more than one parameter in its constructor or static factory method, the Builder pattern is a good choice for designing such a class, especially if most of the parameters are optional. Client code using the Builder pattern is easier to read and write than using the traditional overlapping constructor pattern, and the Builder is more secure than Javabeans.