带你读懂《Effective Java》英文版(Item2)

195 阅读23分钟

Item 2: Consider a builder when faced with many constructor parameters当构造方法参数过多时考虑使用 Builder 模式

    Static factories and constructors share a limitation 限制: they do not scale 处理;调节 well to large numbers of optional parameters. Consider the case of a class representing the Nutrition Facts label that appears on packaged foods. These labels have a few required fields—serving size, servings per container, and calories per serving— and more than twenty optional fields—total fat, saturated fat, trans fat, cholesterol, sodium, and so on. Most products have nonzero values for only a few of these optional fields.

  • Static factories and constructors share a limitation: they do not scale well to large numbers of optional parameters. 静态工厂方法和构造器都有一个共同的缺陷:不能很好地处理大量的可选参数。
  • Consider the case of a class representing the Nutrition Facts label that appears on packaged foods. 考虑如下一个class的例子,这个class代表包装食品中的营养成分标签。
  • These labels have a few required fields—serving size, servings per container, and calories per serving— and more than twenty optional fields—total fat, saturated fat, trans fat, cholesterol, sodium, and so on. 这些标签包含一些必填字段--份量、每罐份量、每罐卡路里,以及超过20多种可选字段--总脂肪、饱和脂肪、反式脂肪、胆固醇、纳等。
  • Most products have nonzero values for only a few of these optional fields.大多数产品都有非零值和少数的可选字段。

    What sort of constructors or static factories should you write for such a class? Traditionally, programmers have used the telescoping constructor pattern, in which you provide a constructor with only the required parameters, another with a single optional parameter, a third with two optional parameters, and so on, culminating [culminate 达到顶点] in a constructor with all the optional parameters. Here’s how it looks in practice. For brevity’s [brevity 简洁;简短] sake, only four optional fields 字段 are shown:

  • What sort of constructors or static factories should you write for such a class? 构造这样的一个calss,你用构造器和静态工厂方法你会写成怎么样?
  • Traditionally, programmers have used the telescoping constructor pattern, in which you provide a constructor with only the required parameters, another with a single optional parameter, a third with two optional parameters, and so on, culminating in a constructor with all the optional parameters. 传统上,程序员一般使用可伸缩(重叠/嵌套)的构造器模式,首先是提供一个所有必填字段的构造函数,其次是在这个基础上再加上一个可选字段的构造函数,再接着是加了两个可选字段的构造函数,以此类推,最后是所有的可选字段都包含的构造函数(这个构造函数也包含所有必填的字段)。
  • Here’s how it looks in practice. For brevity’s sake, only four optional fields are shown:下面是实际使用的情形,为了简洁起见,下面只用了4个可选字段来展示:
// 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; 
    } 
}

    When you want to create an instance, you use the constructor with the shortest parameter list containing all the parameters 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 forced to pass a value for them anyway. In this case, we passed a value of 0 for fat. With “only” six parameters this may not seem so bad, but it quickly gets out of hand 失去控制 as the number of parameters increases.

  • Typically this constructor invocation will require many parameters that you don’t want to set, but you’re forced to pass a value for them anyway. In this case, we passed a value of 0 for fat. 通常来说,这个构造函数要求你传入很多你不需要的参数,但是你又不得不为它们传值。比如上面的例子里面,我们就把fat字段的值设为0。
  • With “only” six parameters this may not seem so bad, but it quickly gets out of hand as the number of parameters increases. 只有6个参数的时候看起来不是很糟糕,但是当参数逐渐增多的时候就会很快失去控制。

    In short, the telescoping constructor pattern works, but it is hard to write client code when there are many parameters, and harder still to read it. The reader is left wondering what all those values mean and must carefully count parameters to find out. Long sequences of identically 一样;相同 typed parameters can cause subtle 不易察觉的;微妙的 bugs. If the client accidentally reverses 颠倒;反转 two such parameters, the compiler won’t complain, but the program will misbehave at runtime (Item 51).

  • In short, the telescoping constructor pattern works, but it is hard to write client code when there are many parameters, and harder still to read it. 简单来说,可伸缩的构造器工作的很好,但是较多参数的情况下,很难编写客户端代码,而且也很难读懂它。
  • The reader is left wondering what all those values mean and must carefully count parameters to find out. 读这段代码的人不禁想知道这些值得含义,但必须仔细地计算参数个数才能找出这个构造器。
  • Long sequences of identically typed parameters can cause subtle bugs. 一连串相同类型的参数可能会导致不易察觉的bug。
  • If the client accidentally reverses two such parameters, the compiler won’t complain, but the program will misbehave at runtime (Item 51). 如果客户端不小心颠倒了两个参数的顺序,编译器是不会报错的,但是运行的时候会产生行为错误。

现在的开发方式一般都是贫血模式+注入模型(Spring框架),自己写的类大部分都不会使用构造器来实例化对象了。

    A second alternative when you’re faced with many optional parameters in a constructor is the JavaBeans pattern, in which you call a parameterless 无参的 constructor to create the object and then call setter methods to set each required parameter and each optional parameter of interest:

  • 当构造器面临很多可选参数时,另一种可选方案是Java Bean模式,这个模式中你只需要调用无参构造器创建一个对象,然后调用setter方法为你必填和可选的参数赋值:
// 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() { }
    // 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; }
}

    This pattern has none of the disadvantages of the telescoping constructor pattern. It is easy, if a bit wordy, to create instances, and easy to read the resulting code:

  • 这个模式不会有伸缩式构造器模式的缺陷。虽然有点冗长,但是创建实例容易且易于阅读:
    NutritionFacts cocaCola = new NutritionFacts(); 
    cocaCola.setServingSize(240); 
    cocaCola.setServings(8); 
    cocaCola.setCalories(100); 
    cocaCola.setSodium(35);
    cocaCola.setCarbohydrate(27);

    Unfortunately, the JavaBeans pattern has serious disadvantages of its own. Because construction is split across multiple calls, a JavaBean may be in an inconsistent 不一致的;反复无常的 state partway 中途 through its construction. The class does not have the option of enforcing consistency merely by checking the validity 有效性;合法性 of the constructor parameters. Attempting to use an object when it’s in an inconsistent state may cause failures that are far removed from the code containing the bug and hence difficult to debug. A related disadvantage is that the JavaBeans pattern precludes 妨碍;阻止;排除 the possibility of making a class immutable (Item 17) and requires added effort on the part of the programmer to ensure thread safety.

  • Unfortunately, the JavaBeans pattern has serious disadvantages of its own. 坏消息是,JavaBean模式本身有一个严重的缺陷。
  • Because construction is split across multiple calls, a JavaBean may be in an inconsistent state partway through its construction. 由于构造过程拆分成了多次调用,所以在构造过程中JavaBean可能会处于不一致的状态。
  • The class does not have the option of enforcing consistency merely by checking the validity of the constructor parameters. 这个类没有仅通过检查入参的有效性来强制类的一致性的途径(选项),merely by checking the validity of the constructor parameters 修饰 enforcing consistency。
  • Attempting to use an object when it’s in an inconsistent state may cause failures (that are far removed from the code / containing the bug) and hence difficult to debug. 在对象不一致的状态下试图使用它很容易产生错误,而且这些错误不是代码上写的bug产生的,很难去调试复现。that are far removed from the code修饰failures,containing the bug修饰the code。
  • A related disadvantage is that the JavaBeans pattern precludes the possibility of making a class immutable (Item 17) and requires added effort on the part of the programmer to ensure thread safety. 与之相关的一个缺陷是,JavaBean模式让class失去了成为不可变类的可能性,这需要程序员花额外的心思去保证线程安全。

    It is possible to reduce these disadvantages by manually “freezing” the object when its construction is complete and not allowing it to be used until frozen, but this variant 变种;变形 is unwieldy and rarely used in practice. Moreover, it can cause errors at runtime because the compiler cannot ensure that the programmer calls the freeze method on an object before using it.

  • It is possible to reduce these disadvantages by manually “freezing” the object when its construction is complete and not allowing it to be used until frozen, but this variant is unwieldy and rarely used in practice. 如果在构造未完成之前手工冻结对象让它不能被使用,可以减少这些缺陷,但是这个变种在实践中很难并且很少使用。
  • Moreover, it can cause errors at runtime because the compiler cannot ensure that the programmer calls the freeze method on an object before using it. 更重要的是,由于编译器并不会去确保程序员在使用对象之前会去调用“冻结”方法,所以在运行过程中可能会产生错误。

一般来说,现在开发很少关注一致性的问题,因为大多数都是使用Spring框架进行注入,基本上只有实体(Entity)类才需要自己实例化,且普遍是局部变量,不需要考虑一致性(线程安全)的问题。

    Luckily, there is a third alternative that combines the safety of the telescoping constructor pattern with the readability of the JavaBeans pattern. It is a form of the Builder pattern [Gamma95]. Instead of making the desired 期望得到的 object directly, the client calls a constructor (or static factory) with all of the required parameters and gets a builder object. Then the client calls setter-like methods on the builder object to set each optional parameter of interest. Finally, the client calls a parameterless build method to generate the object, which is typically immutable. The builder is typically a static member class (Item 24) of the class it builds. Here’s how it looks in practice:

  • Luckily, there is a third alternative that combines the safety of the telescoping constructor pattern with the readability of the JavaBeans pattern. 幸运的是,有第三种可替代的方案,即兼容了可伸缩构造器的安全性也兼容了JavaBean模式的可读性。
  • It is a form of the Builder pattern [Gamma95]. Instead of making the desired object directly, the client calls a constructor (or static factory) with all of the required parameters and gets a builder object. 它是建造者模式的一种形式。与直接创建对象不同,客户端需要调用一个包含所有必填字段的构造器(静态工厂),然后通过这个构造器(静态工厂)拿到一个bulider对象。
  • Then the client calls setter-like methods on the builder object to set each optional parameter of interest. 然后客户端调用builder对象中类似setter的方法,给你所需的可选字段赋值。
  • Finally, the client calls a parameterless build method to generate the object, which is typically immutable. 最后,客户端调用一个无参的build()方法来生成对象,而且生成的对象通常是不可变的。
  • The builder is typically a static member class (Item 24) of the class it builds. Here’s how it looks in practice: Builder类一般是它构建的类的静态成员类,下面是它实际使用的样子:
// 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() { 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; 
    }
}

    The NutritionFacts class is immutable, and all parameter default values are in one place. The builder’s setter methods return the builder itself so that invocations can be chained, resulting in a fluent API. Here’s how the client code looks:

  • NutritionFacts类是不可变的,所有参数的默认值都在一个地方。Builder中的setter方法都返回它自身,所以setter方法的调用可以连接起来,形成流式API。客户端的代码如下所示:
    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 simulates 模拟;模仿;装作 named optional parameters as found in Python and Scala.

  • 客户端代码易于编写,且易于阅读。Builder模式模拟Python和Scala中的命名可选参数。

    Validity checks were omitted 省略 for brevity. To detect 发现;查明;侦查 invalid 非法的;无效的 parameters as soon as possible, check parameter validity in the builder’s constructor and methods. Check invariants 不变式;非变量 involving multiple parameters in the constructor invoked by the build method. To ensure these invariants against attack, do the checks on object fields after copying parameters from the builder (Item 50). If a check fails, throw an IllegalArgumentException (Item 72) whose detail message indicates which parameters are invalid (Item 75).

  • Validity checks were omitted for brevity. To detect invalid parameters as soon as possible, check parameter validity in the builder’s constructor and methods. 为了简洁起见,代码中省略了有效性的校验。如果想要尽快地检测出无效的入参,那么可以在Builder的构造器和(Setter)方法里面直接校验参数的有效性。
  • Check invariants involving multiple parameters in the constructor invoked by the build method. Check invariants in the constructor是主句,翻译为:检查构造函数中的不变值;involving multiple parameters修饰invariants,翻译为:涉及多个参数的不变值;invoked by the build method又是修饰constructor,翻译为:被build方法调用的构造函数;连起来就是,检查/build方法调用的构造函数里面/涉及多个参数的不变量。这句话其实说的不明所以了,建议跳过。
  • To ensure these invariants against attack, do the checks on object fields after copying parameters from the builder (Item 50). If a check fails, throw an IllegalArgumentException (Item 72) whose detail message indicates which parameters are invalid (Item 75). 为了防止这些不变值被攻击,可以在builder中复制完参数后校验对象的字段值。如果有一个字段校验失败,那么抛出IllegalArgumentException异常来表示这个参数是非法的。>>>比如说上面的例子要做校验的话,那么在build方法里面做校验即可。
public NutritionFacts build() { 
    NutritionFacts result = new NutritionFacts(this);
    //方法里面校验参数的合法性
    validateParams(result);
    return result;
} 

private void validateParams(NutritionFacts object) {
    if (...) {
        throw new IllegalArgumentException();
    }
}

上面都是描述建造者模式的使用方法,具体详情可以自行百度,有很多设计模式的书都描述的很清楚了。下面,作者给出了一个精妙的设计,使得抽象类(父类)和子类的方法都能使用链式调用从而形成流式API。

    The Builder pattern is well suited to class hierarchies 层级;等级制度. Use a parallel 平行的 hierarchy of builders, each nested 镶嵌 in the corresponding class. Abstract classes have abstract builders; concrete classes have concrete builders. For example, consider an abstract class at the root of a hierarchy representing various kinds of pizza:

  • The Builder pattern is well suited to class hierarchies. Use a parallel hierarchy of builders, each nested in the corresponding class. 建造者模式也适合类的层级结构。使用平行层级的Builder,每个Builder都嵌套在相应的类中。
  • Abstract classes have abstract builders; concrete classes have concrete builders. 抽象类里有抽象的建造器;具体类中则是具体建造器。
  • For example, consider an abstract class at the root of a hierarchy representing various kinds of pizza: 例如说,考虑如下一个层级在最顶层的抽象类,这个抽象类代表各种类型的pizza:
// 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();
        // Subclasses must override this method to return "this" 
        protected abstract T self();
    } 
    Pizza(Builder<?> builder) { 
        toppings = builder.toppings.clone(); // See Item 50
    } 
} 

    Note that Pizza.Builder is a generic 通用的;一般的 type with a recursive 递归的;循环的 type parameter (Item 30). This, along with 随同;跟...一起 the abstract self method, allows method chaining to work properly in subclasses, without the need for casts. This workaround 应变方法;变通 for the fact that Java lacks 缺乏;匮乏 a self type is known as the simulated 模拟;模仿;装作 self-type idiom 习语;成语.

  • Note that Pizza.Builder is a generic type with a recursive type parameter (Item 30). This, along with the abstract self method, allows method chaining to work properly in subclasses, without the need for casts. 注意:Pizza.Builder是一个带有递归性质参数的泛型类型(这里是指代码中的<T extends Bulider>),搭配self()这个抽象方法,可以让方法的链式调用兼容子类,并且不需要转换强制转换。PS:这句话很重要,主要说明父类method和子类method能够混合使用链式调用是如何实现的。
  • This workaround for the fact that Java lacks a self type is known as the simulated self-type idiom. 这种由于java缺少自身类型的变通,可以看作是模拟自我类型的惯用用法。

    Here are two concrete subclasses of Pizza, one of which represents a standard New-York-style pizza, the other a calzone. The former has a required size parameter, while the latter lets you specify 指定;规定 whether sauce should be inside or out:

  • Here are two concrete subclasses of Pizza, one of which represents a standard New-York-style pizza, the other a calzone. 这里有两个具体的Pizza子类,一个代表标准的New-York-style pizza,另一个则是calzone。
  • The former has a required size parameter, while the latter lets you specify whether sauce should be inside or out: 前者需要一个size的入参,后者则需要你指定sauce在里面还是外面:
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 
        //这里返回Pizza的子类NyPizza,被作者称为协变式返回类型
        public NyPizza build() { 
            return new NyPizza(this);
        } 
        @Override 
        protected Builder self() { 
            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() { 
            sauceInside = true; 
            return this;
        }
        @Override 
        //这里返回Pizza的子类Calzone,被作者称为协变式返回类型
        public Calzone build() { 
            return new Calzone(this);
        } 
        @Override 
        protected Builder self() { 
            return this; 
        } 
    }
    private Calzone(Builder builder) { 
        super(builder); 
        sauceInside = builder.sauceInside;
    }
}

    Note that the build method in each subclass’s builder is declared to return the correct subclass: the build method of NyPizza.Builder returns NyPizza, while the one in Calzone.Builder returns Calzone. This technique, wherein 其中;在哪里 a subclass method is declared to return a subtype of the return type declared in the superclass, is known as covariant 协变式;协变量 return typing. It allows clients to use these builders without the need for casting.

  • Note that the build method in each subclass’s builder is declared to return the correct subclass: the build method of NyPizza.Builder returns NyPizza, while the one in Calzone.Builder returns Calzone.注意:子类Builder中的build方法返回的类型是对应的子类:NyPizza.Builder的build()返回NyPizza,Calzone.Builder的build()返回Calzone。
  • This technique, wherein a subclass method is declared to return a subtype of the return type declared in the superclass, is known as covariant return typing. It allows clients to use these builders without the need for casting. 子类方法里面定义的返回类型是超类方法返回类型的子类(上面代码中的abstract Pizza build();子类具体实现返回的是Pizza的子类),这种技术被称为协变式返回类型。而且这种技术可以让客户端无需转化类型直接使用Builder模式

    The client code for these “hierarchical builders” is essentially 本质上;根本上 identical 相同的;同一的 to the code for the simple NutritionFacts builder. The example client code shown next assumes static imports on enum constants for brevity:

  • "层级结构的Builder"的客户端代码写法跟NutritionFacts的builder写法完全一样。为了简洁起见,下面的客户端代码enum变量的导入假设为静态导入:
    NyPizza pizza = new NyPizza
                .Builder(SMALL)
                .addTopping(SAUSAGE)
                .addTopping(ONION)
                .build();
    Calzone calzone = new Calzone
                .Builder() 
                .addTopping(HAM)
                .sauceInside()
                .build();

    A minor 轻微的;较小的 advantage of builders over constructors is that builders can have multiple varargs parameters because each parameter is specified in its own method. Alternatively, builders can aggregate 合计;总计;聚集 the parameters passed into multiple calls to a method into a single field, as demonstrated 说明;表示 in the addTopping method earlier.

  • A minor advantage of builders over constructors is that builders can have multiple varargs parameters because each parameter is specified in its own method. 建造者模式对于构造器有一个微弱的优势,由于参数可以在Builder里面指定,所以Builder可以有多个可变参数。
  • Alternatively, builders can aggregate the parameters passed into multiple calls to a method into a single field, as demonstrated in the addTopping method earlier. 稍微拆一下builders can aggregate the parameters(that were passed into multiple calls to a method) into a single field。主句builders can aggregate the parameters into a single field:Builder可以把参数聚合成一个单一的字段;the parameters that were passed into multiple calls to a method :参数传递给一个方法的多次调用;组合起来就是:或者说,Builder可以把一个方法中多次调用的参数聚合成一个字段,就像上面所说的addTopping()方法。
  • 稍微说明一下:the parameters是指上面的SAUSAGE、ONION,a method值得是addTopping()方法,multiple calls是指NyPizza.Builder(SMALL).addTopping(SAUSAGE).addTopping(ONION)多次调用了addTopping(),a single field是指Pizza中的toppings字段。按这样理解的话,就是说,建造者模式可以把SAUSAGE、ONION这样的参数,通过多次调用toppings()方法,聚合成toppings字段。

    The Builder pattern is quite flexible. A single builder can be used repeatedly to build multiple objects. The parameters of the builder can be tweaked 扭;拧;扯 between invocations of the build method to vary the objects that are created. A builder can fill in 填平;填充 some fields automatically upon object creation, such as a serial number that increases each time an object is created.

  • The Builder pattern is quite flexible. A single builder can be used repeatedly to build multiple objects. 建造器模式非常灵活。一个Builder可以重复使用来构建多个对象。
  • The parameters of the builder can be tweaked between invocations of the build method to vary the objects that are created. Builder的参数可以在构建方法的调用过程中进行调整,从而创建不同的对象。
  • A builder can fill in some fields automatically upon object creation, such as a serial number that increases each time an object is created. Builder可以在创建对象的时候自动填充一些字段(属性),就比如说每次创建对象时增加的序列号。

    The Builder pattern has disadvantages as well. In order to create an object, you must first create its builder. While the cost of creating this builder is unlikely to be noticeable in practice, it could be a problem in performance-critical situations. Also, the Builder pattern is more verbose than the telescoping constructor pattern, so it should be used only if there are enough parameters to make it worthwhile, say four or more. But keep in mind 牢记;放在心上 that you may want to add more parameters in the future. But if you start out with constructors or static factories and switch to a builder when the class evolves 逐渐演变;进化;形成 to the point where the number of parameters gets out of hand, the obsolete 过时的 constructors or static factories will stick out like a sore thumb 特别扎眼. Therefore, it’s often better to start with a builder in the first place.

  • The Builder pattern has disadvantages as well. In order to create an object, you must first create its builder. Builder模式一样也有缺点。为了创建一个对象,你得先创建对象的Builder。
  • While the cost of creating this builder is unlikely to be noticeable in practice, it could be a problem in performance-critical situations. 在实际使用中,这个Builder的创建成本可能不太会去注意,但是在高性能的场景下就是个问题了。
  • Also, the Builder pattern is more verbose than the telescoping constructor pattern, so it should be used only if there are enough parameters to make it worthwhile, say four or more. 而且Builder模式比起伸缩式构造器更加冗长,只有在足够多参数的情况下才值得去使用它,比如说四个或者更多。
  • But keep in mind that you may want to add more parameters in the future. But if you start out with constructors or static factories and switch to a builder when the class evolves to the point where the number of parameters gets out of hand, the obsolete constructors or static factories will stick out like a sore thumb. 但是,有一点你要记住,当你未来希望加更多的参数时。而你一开始是使用构造器或者是静态工厂方法,由于后续迭代参数数量逐渐失控切换到Builder模式,此时废弃的构造器或者是静态工厂方法就显得格外多余。
  • Therefore, it’s often better to start with a builder in the first place. 因此,一开始就使用Builder模式是最好的。

    In summary, the Builder pattern is a good choice when designing classes whose constructors or static factories would have more than a handful of parameters, especially if many of the parameters are optional or of identical type. Client code is much easier to read and write with builders than with telescoping constructors, and builders are much safer than JavaBeans.

  • 总而言之,在设计类时构造器或者静态方法包含大量参数的时候,Builder模式是一个很好地选择,尤其是有很多可选参数或者很多相同类型的参数时。而且比起伸缩式构造器,客户端代码易读易写,比起JavaBeans更安全。

现在来考虑一个问题,给你一个父类Fruit(水果)和一个子类Apple(苹果),现在要你通过Builder模式来创建Apple对象(包含父类的字段),你该怎么写?如下:

public class Fruit {
    private String name;
}

public class Apple extends Fruit {
    private String color;
}

仿照作者所说的"模拟自我类型",下面就来实现一下他们的Builder模式,简易版本如下:

//父类Fruit
public class Fruit {

    private String name;

    public String getName() {
        return name;
    }

    public static class Builder<T extends Builder<T>> {
        private String name;

        public T name(String name) {
            this.name = name;
            return self();
        }

        public T self() {
            return (T) this;
        }

        public Fruit build() {
            return new Fruit(this);
        }
    }

    public static Builder builder() {
        return new Builder();
    }

    public Fruit(Builder builder) {
        this.name = builder.name;
    }
}
public class Apple extends Fruit {

    private String color;

    public String getColor() {
        return color;
    }

    public static class Builder extends Fruit.Builder<Builder> {

        private String color;

        public Builder color(String color) {
            this.color = color;
            return self();
        }

        @Override
        public Builder self() {
            return super.self();
        }

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

    public static Builder builder() {
        return new Builder();
    }

    public Apple(Builder builder) {
        super(builder);
        this.color = builder.color;
    }
}
//main方法
public static void main(String[] args) {
    Apple apple = Apple.builder().name("apple").color("red").build();
    System.out.println(apple.getName());
    System.out.println(apple.getColor());
}
/*
*输出:
apple
red
*/

现在用的最多的lombok,里面也提供了SuperBuilder注解来实现层级关系的Builder模式,具体怎么实现是类似的,有兴趣可以看看,下面是v1.18.4的部分编译代码:

public class Fruit {
    private String name;

    protected Fruit(FruitBuilder<?, ?> b) {
        this.name = b.name;
    }

    public static FruitBuilder<?, ?> builder() {
        return new FruitBuilderImpl();
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private static final class FruitBuilderImpl extends FruitBuilder<Fruit, FruitBuilderImpl> {
        private FruitBuilderImpl() {
        }

        protected FruitBuilderImpl self() {
            return this;
        }

        public Fruit build() {
            return new Fruit(this);
        }
    }

    public abstract static class FruitBuilder<C extends Fruit, B extends FruitBuilder<C, B>> {
        private String name;

        public FruitBuilder() {
        }

        protected abstract B self();

        public abstract C build();

        public B name(String name) {
            this.name = name;
            return this.self();
        }

        public String toString() {
            return "Fruit.FruitBuilder(name=" + this.name + ")";
        }
    }
}
public class Apple extends Fruit {
    private String color;

    protected Apple(AppleBuilder<?, ?> b) {
        super(b);
        this.color = b.color;
    }

    public static AppleBuilder<?, ?> builder() {
        return new AppleBuilderImpl();
    }

    public String getColor() {
        return this.color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    private static final class AppleBuilderImpl extends AppleBuilder<Apple, AppleBuilderImpl> {
        private AppleBuilderImpl() {
        }

        protected AppleBuilderImpl self() {
            return this;
        }

        public Apple build() {
            return new Apple(this);
        }
    }

    public abstract static class AppleBuilder<C extends Apple, B extends AppleBuilder<C, B>> extends Fruit.FruitBuilder<C, B> {
        private String color;

        public AppleBuilder() {
        }

        protected abstract B self();

        public abstract C build();

        public B color(String color) {
            this.color = color;
            return this.self();
        }

        public String toString() {
            return "Apple.AppleBuilder(super=" + super.toString() + ", color=" + this.color + ")";
        }
    }
}