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


当前回答

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

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

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

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

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

其他回答

下面是在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实践,我强烈推荐它。

以餐厅为例。“今天的饭菜”的创建是一种工厂模式,因为您告诉厨房“给我今天的饭菜”,然后厨房(工厂)根据隐藏的标准决定生成什么对象。

如果你订购了一个定制的披萨,建造者就会出现。在这种情况下,服务员告诉厨师(建筑工人)“我需要一个披萨;加奶酪、洋葱和培根!”因此,构建器公开了生成的对象应该具有的属性,但隐藏了如何设置这些属性。

在浏览微软MVC框架时,我对构建器模式有了一些想法。我在ControllerBuilder类中遇到了这种模式。这个类返回控制器工厂类,然后用它来构建具体的控制器。

我认为使用构建器模式的优点是,您可以创建自己的工厂并将其插入到框架中。

@Tetha,可以有一家意大利人开的餐厅(框架),供应披萨。为了准备披萨,Italian guy (Object Builder)使用Owen (Factory)和一个披萨基(基类)。

现在印度人从意大利人手里接管了餐厅。印度餐厅(框架)提供dosa而不是披萨。为了准备dosa, Indian guy(对象构建器)使用fried Pan(工厂)和Maida(基类)

如果你看场景,食物是不同的,食物的制作方式是不同的,但在同一家餐厅(在同一框架下)。餐馆应该以这样一种方式建造,它可以支持中国菜、墨西哥菜或任何菜。框架内的对象构建器有助于你想要的插件料理。例如

class RestaurantObjectBuilder
{
   IFactory _factory = new DefaultFoodFactory();

   //This can be used when you want to plugin the 
   public void SetFoodFactory(IFactory customFactory)
   {
        _factory = customFactory;
   }

   public IFactory GetFoodFactory()
   {
      return _factory;
   }
}

我在自制消息库中使用了builder。库核心从线路接收数据,用Builder实例收集数据,然后,一旦Builder确定它已经获得了创建Message实例所需的一切,Builder. getmessage()就使用从线路收集的数据构造消息实例。

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

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

请参阅Groovy文档中的构建器