一尘不染

Java 什么时候使用构建器模式?

java

什么是一些常见的,现实世界的例子使用Builder模式的?它能买到什么?为什么不只使用工厂模式?


阅读 634

收藏
2020-02-25

共2个答案

一尘不染

构建器和工厂恕我直言之间的主要区别在于,当你需要做很多事情来构建对象时,构建器非常有用。例如,想象一个DOM。你必须创建大量节点和属性才能获得最终对象。当工厂可以在一个方法调用中轻松创建整个对象时,将使用工厂。

使用构建器的一个示例是构建XML文档,例如在构建HTML片段时就使用了此模型,例如,我可能具有用于构建特定类型表的构建器,并且可能具有以下方法(未显示参数):

BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()

然后,该构建器将为我吐出HTML。这比遍历大型过程方法更容易阅读。

2020-02-25
一尘不染

以下是争论在Java中使用模式和示例代码的一些原因,但这是在设计模式中由四人组成的Builder模式的实现。在Java中使用它的原因也适用于其他编程语言。

当设计其构造函数或静态工厂将具有多个参数的类时,构造器模式是一个不错的选择。

在某个时候,我们所有人都遇到了一个带有构造函数列表的类,其中每个添加项都会添加一个新的option参数:

Pizza(int size) { ... }        
Pizza(int size, boolean cheese) { ... }    
Pizza(int size, boolean cheese, boolean pepperoni) { ... }    
Pizza(int size, boolean cheese, boolean pepperoni, boolean bacon) { ... }

这称为伸缩构造函数模式。这种模式的问题在于,一旦构造函数具有4或5个参数,就很难记住参数的必需顺序以及在给定情况下可能需要的特定构造函数。

伸缩构造器模式的一种替代方法是JavaBean模式,你可以在其中调用带有必需参数的构造器,然后在之后调用任何可选的setter:

Pizza pizza = new Pizza(12);
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);

这里的问题是,由于对象是通过多个调用创建的,因此在其构造过程中可能处于不一致状态。这也需要付出很多额外的努力来确保线程安全。

更好的选择是使用构建器模式。

public class Pizza {
  private int size;
  private boolean cheese;
  private boolean pepperoni;
  private boolean bacon;

  public static class Builder {
    //required
    private final int size;

    //optional
    private boolean cheese = false;
    private boolean pepperoni = false;
    private boolean bacon = false;

    public Builder(int size) {
      this.size = size;
    }

    public Builder cheese(boolean value) {
      cheese = value;
      return this;
    }

    public Builder pepperoni(boolean value) {
      pepperoni = value;
      return this;
    }

    public Builder bacon(boolean value) {
      bacon = value;
      return this;
    }

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

  private Pizza(Builder builder) {
    size = builder.size;
    cheese = builder.cheese;
    pepperoni = builder.pepperoni;
    bacon = builder.bacon;
  }
}

请注意,Pizza是不可变的,并且参数值都位于单个位置。因为Builder的setter方法返回了Builder对象,所以它们可以被链接。

Pizza pizza = new Pizza.Builder(12)
                       .cheese(true)
                       .pepperoni(true)
                       .bacon(true)
                       .build();

这将导致易于编写,非常易于阅读和理解的代码。在此示例中,可以将构建方法修改为在将参数从构建器复制到Pizza对象后检查参数,如果提供了无效的参数值,则抛出IllegalStateException。这种模式很灵活,将来很容易为它添加更多参数。仅当你要为构造函数使用4个或5个以上的参数时,它才真正有用。就是说,如果你怀疑将来可能会添加更多参数,那么首先值得这样做。

2020-02-25