Effective Java —— 多字段下考虑使用建造者模式构建实例

本文参考

本篇文章参考自《Effective Java》第三版第二条"Consider a builder when faced with many constructor parameters"

静态工厂方法和构造器的不足之处

当一个类中有大量的字段时,尽管能通过设置不同的形参列表和方法名进行重载或使用静态工厂方法进行一定的简化,但我们还是会难以避免地为方法签名设置了较长的形参列表并直接使用到这些方法,以致于"it is hard to write client code when there are many parameters, and harder still to read it",原文将这种颇为不优美的解决方案称为"telescoping constructor pattern"(重叠构造器模式)

另一种不优美的解决方案是"JavaBean pattern"(JavaBean模式),我们需要频繁地调用set方法来构造我们需要的实例,只要有一个set方法没有调用,那么构造出来的实例就是不完整的,而且JavaBean模式使得把类做成不可变的可能性不复存在

a JavaBean may be in an inconsistent state partway through its construction and the JavaBeans pattern precludes the possibility of making a class immutable

 

Builder pattern(建造者模式)示例

我们不直接创建我们需要的实例,首先仅设置我们必需的字段值,返回一个Builder 实例

然后,上一步返回的Builder 实例调用类似set的方法来设置其它可选的字段值,同样,返回的都是Builder 实例

最后,Builder实例调用无参的build()方法,生成我们真正需要的实例,这种设计模式实现了类的不可变性

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 of the class it builds.

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;
  }

  public static void main(String[] args) {
    NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
                                .calories(100)
                                .sodium(35)
                                .carbohydrate(27)
                                .build();
  }
}

NutritionFacts类的字段在Builder静态内部类中有一份一模一样的声明,并且默认参数值都在Builder类中,NutritionFacts类本身不具备任何为字段"直接"赋值的构造方法,而是由Builder实例"间接"完成

注意Builder的所有"set"方法都返回Builder的实例,使得我们可以链式调用其它"set"方法,原文将这种特性称为"fluent API",我们还可以根据具体情况判断是否需要为Builder的方法检查传入参数的有效性

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

 

The Builder pattern is well suited to class hierarchies

Builder类也适用于类层次结构,抽象类有抽象的 builder,具体类有具体的 builder,下面是原文抽象类的示例

abstract class Pizza {
  public enum Topping {HAM, MUSHROOM, ONION, PEPPER, SAUSAGE}

  final Set<Topping> toppings;

  // recursive type parameter
  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();
  }
}

下面是两个具体的实现类

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;
  }
}

注意在子类中,build()方法的返回类型是子类本身,而不是抽象类,否则就需要我们在调用build()方法后,将返回的抽象类进行强制类型转换

posted @ 2020-04-21 15:47  咕~咕咕  阅读(473)  评论(0编辑  收藏  举报