Builder 模式
幸运的是, 这里还有一种方法 Builder 模式, 兼顾 重叠构造器 的安全以及 Javabean 模式 的可读性.
客户端先通过调用构造器或者静态工厂方法, 传入必须的参数, 获得一个 builder 对象, 代替直接获取目标对象. 然后客户端在该 builder 对象上调用 setXXX 方法, 为每一个感兴趣的可选属性赋值, 最后客户端调用一个 无参构造器 生成最终的目标对象, 该对象一般是不可变的. 其中 Builder 类是目标类的静态内部类
示例代码:
java ssss
其中 NutritionFacts 类为不可变类, 类的成员变量全部被 final 修饰, 参数的默认值被放在一个地方. Builder 类 setXXX 方法返回 Builder 本身, 这种写法, 可以将设置变成一个链, 一直点下去 (fluent APIs):
- NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
- .calories(100)
- .sodium(35)
- .carbohydrate(27)
- .build();
这样的客户端代码, 容易编写, 更容易阅读.
示例代码中, 为了简洁, 省去了有效性的检查. 一般, 为了尽快的检查到非法参数, 我们在 builder 的构造器和方法中, 对其参数进行检查.
还需要检查 build 方法中调用的构造器的多个不可变参数 2. 这次检查延迟到 object 中, 为了确保这些不可变参数不受到攻击, 在 builder 将属性复制到 object 中的时候, 再做一次检查. 如果检验失败, 则抛出 IllegalArgumentException 异常, 异常信息中提示哪些参数不合法.
Bulider 模式很适合类的层次结构. 可以使用一个 builder 的平行结构, 即每一个 builder 嵌套在一个对应的类中, 抽象类中有抽象的 builder , 具体类中有具体的 builder . 像下面的代码所示:
- // Builder pattern for class hierarchies
- 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
- }
- }
- 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() {
- return new NyPizza(this);
- }
- @Override
- protected Builder self() {
- return this;
- }
- }
- private NyPizza(Builder builder) {
- super(builder);
- size = builder.size;
- }
- }
- 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
- public Calzone build() {
- return new Calzone(this);
- }
- @Override
- protected Builder self() {
- return this;
- }
- }
- private Calzone(Builder builder) {
- super(builder);
- sauceInside = builder.sauceInside;
- }
- }
注意, 这里的 Pizza.Builder 是类属性, 被 static 修饰的, 并且泛型参数, 是一个 递归 的泛型参数, 继承本身. 和返回自身的抽象方法 self , 搭配一起, 可以链式的调用下去, 不需要进行类型的转换, 这样做的原因是, java 不直接支持 自类型 3, 可以模拟自类型 4.
如果不使用模拟自类型的话, 调用 addTopping 方法, 返回的其实就是抽象类中的 Builder , 这样就导致无法调用子类扩展方法, 无法使用 fluent APIS. 其中 build 方法, 使用了 1.5 添加的 协变类型 , 它可以不用 cast 转换, 就直接使用具体的类型, 否则子类接收父类, 是需要强转的 .
builder 模式另外一个小优点: builder 可以有多个 可变参数, 因为, 可以将多个可变参数, 放到各自对应的方法中 5. 另外 build 可以将多个参数合并到一个字段上, 就如上面代码中 addTopping 的那样 6.
builder 模式是非常灵活的. 一个单一的 builder 多次调用, 可以创建出不同的对象 7.builder 的参数, 可以在调用 build 方法的时候进行细微调整, 以便修改创建出的对象 8.builder 模式还可以自动的填充 object 域的字段在创建对象的时候. 比如为每个新创建的对象设置编号, 只需要在 builder 中维护一个类变量即可.
builder 模式也是有缺点的. 为了创建一个对象, 我们首先需要创建它的 builder 对象. 虽然, 创建 builder 对象的开销, 在实践中不是很明显, 但是在对性能要求很严格的场景下, 这种开销能会成为一个问题. 同时, builder 模式是非常冗杂的, 对于比 重叠构造器 , 所以, builder 模式应该仅仅被用在构造器参数足够多的情况下, 比如三个, 四个或者更多, 只有这样, 使用 builder 模式才是值得的. 但是, 你要时刻记住, 类在将来可能会添加新的参数, 如果你一开始使用了构造器或者静态工厂方法, 随着类的变化, 类的属性参数变得足够多, 这时候你再切换到 builder 模式, 那么一开始的构造器和静态工厂方法就会被废弃, 这些废弃的方法看起来很凸出, 你还不能删除它们, 需要保存兼容性. 因此, 一般一开始就选择 builder 模式是一个不错的选择.
总结, builder 模式是一个好的选择, 当设计一个类的时候, 该类的构造器参数或者静态工厂参数不止几个参数, 尤其是许多参数是可选的或者同一个类型 (可变参数). 这样设计的类, 客户端代码, 与静态工厂方法和重叠构造器比起来更加容易阅读和编写, 和 Javabeans 模式比起来更加安全.
来源: https://www.cnblogs.com/young-youth/p/11472610.html