使用构建器模式的一些常见的、真实的例子是什么?它能给你买什么?为什么不直接使用工厂模式?


当前回答

下面是在Java中使用模式和示例代码的一些理由,但它是四人组在设计模式中介绍的构建器模式的实现。在Java中使用它的原因也适用于其他编程语言。

正如Joshua Bloch在Effective Java第二版中所说:

当设计那些构造函数或静态工厂具有多个参数的类时,构建器模式是一个很好的选择。

我们都在某些时候遇到过一个类,它有一个构造函数列表,其中每次添加都会添加一个新的选项形参:

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模式,在该模式中,您调用带有必选参数的构造函数,然后调用以下任何可选设置:

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();

This results in code that is easy to write and very easy to read and understand. In this example, the build method could be modified to check parameters after they have been copied from the builder to the Pizza object and throw an IllegalStateException if an invalid parameter value has been supplied. This pattern is flexible and it is easy to add more parameters to it in the future. It is really only useful if you are going to have more than 4 or 5 parameters for a constructor. That said, it might be worthwhile in the first place if you suspect you may be adding more parameters in the future.

关于这个主题,我从Joshua Bloch的《Effective Java,第二版》一书中借鉴了很多。为了更多地了解这种模式和其他有效的Java实践,我强烈推荐它。

其他回答

下面是在Java中使用模式和示例代码的一些理由,但它是四人组在设计模式中介绍的构建器模式的实现。在Java中使用它的原因也适用于其他编程语言。

正如Joshua Bloch在Effective Java第二版中所说:

当设计那些构造函数或静态工厂具有多个参数的类时,构建器模式是一个很好的选择。

我们都在某些时候遇到过一个类,它有一个构造函数列表,其中每次添加都会添加一个新的选项形参:

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模式,在该模式中,您调用带有必选参数的构造函数,然后调用以下任何可选设置:

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();

This results in code that is easy to write and very easy to read and understand. In this example, the build method could be modified to check parameters after they have been copied from the builder to the Pizza object and throw an IllegalStateException if an invalid parameter value has been supplied. This pattern is flexible and it is easy to add more parameters to it in the future. It is really only useful if you are going to have more than 4 or 5 parameters for a constructor. That said, it might be worthwhile in the first place if you suspect you may be adding more parameters in the future.

关于这个主题,我从Joshua Bloch的《Effective Java,第二版》一书中借鉴了很多。为了更多地了解这种模式和其他有效的Java实践,我强烈推荐它。

当你有很多选择要处理的时候,你就用它。想想jmock这样的东西:

m.expects(once())
    .method("testMethod")
    .with(eq(1), eq(2))
    .returns("someResponse");

这感觉更自然,而且是可能的。

还有xml构建,字符串构建和其他很多东西。想象一下,如果java.util.Map将put作为构建器。你可以这样做:

Map<String, Integer> m = new HashMap<String, Integer>()
    .put("a", 1)
    .put("b", 2)
    .put("c", 3);

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

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

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

然后这个构建器会为我输出HTML。这比遍历一个大型过程方法容易得多。

在维基百科上查看生成器模式。

在前面的回答(双关语)的基础上,有一个很好的现实例子,就是Groovy对Builders的内置支持。

使用Groovy的MarkupBuilder创建XML 使用Groovy的StreamingMarkupBuilder创建XML Swing构建器 SwingXBuilder

请参阅Groovy文档中的构建器

对于多线程问题,我们需要为每个线程构建一个复杂的对象。对象表示正在处理的数据,并且可以根据用户输入进行更改。

我们能用工厂代替吗?是的

为什么我们没有呢?我想建造者更有意义。

工厂用于创建具有相同基本类型的不同类型的对象(实现相同的接口或基类)。

构建器一遍又一遍地构建同一类型的对象,但是构造是动态的,因此可以在运行时更改它。