意图
Builder是创建模式的一种,让你可以使用相同的构建过程生成不同类型和对象的表示形式。BUilder允许一步一步构建复杂对象。
问题
假设有一个复杂对象需要一步一步的初始化许多字段和嵌套对象。这些代码通常放在一个有很多参数的构造方法中,或者更糟糕,分散在客户端代码中。
比如,让我们考虑下如何创建一个House对象。创建一个简单的房子,你需要盖四面墙,安装一个门和两个窗户,构建房顶。但是,如果你想要一个更大,更亮堂,并且有后院和其他好吃的东西的房子呢?
想到的最简单的解决方式是从Hose类扩展,通过创建子类来覆盖所有参数的组合。这种处理方式明显会导致创建许多子类。任何新的参数,比如阳台风格,都需要扩张这个层级甚至扩张很多。
这些参数大多时候是无用的,导致构造方法看起来很丑陋。比如,很多房子没有游泳池。因此,和游泳池相关的所有参数99%情况下都是无用的。
解决
Builder模式建议抽离出类自身的构造代码到不同的对象中,这种对象叫做builder。
模式把对象的构造组织称一系列步骤(比如:buildWalls,BuildDoor等)。创建这个对象,你需要调用一个构造类中的几个构造步骤。最重要的一点是你不需要调用所有的步骤。你只能使用生成对象的特定配置所需的步骤。
通常,对于你正在尝试构建的各种产品,相同的构建步骤将会有所不同(一样步骤,但是每个步骤完成的方式不同)。比如,一个机舱可以用木日志建造,但是一座城堡必须用石头。
在这种情况下,你可以在不同的方式下,创建几个不同的实现了相同构建步骤的builder类。然后,你就可以使用这些builder在相同的构建处理中生产不同类型的对象。
比如,有一个StoneBuilder类来处理所有和石头相关的事情,一个WoodBuilder类来处理木头的。你可以使用它们中的一个通过一系列构造步骤来生产一个石头或者木头房子。这种方式仅在客户端代码通过通用接口来和builder交互时可用。
你可以进一步抽离出整个构建过程到一个特别的类:Director。在这种情况下,Director(Director关联了一个Builder对象)可以定义构建步骤的顺序并执行他们。这种结构下将会向客户端隐藏所有构建细节。
结构
Builder声明构建一个产品需要的步骤。
Concrete Builder提供构造步骤的不同实现。Builder也可以提供获取构造结果的方法。这个方法不需要声明在builder接口中,因为builder可能生产没有遵循通用接口的产品。但是如果你处理的产品是单一层次的,你可以安全的把这个方法描述在基本接口中。
Product是创建结果的一个对象。Builder可以生产不属于同一个类层级或者接口的产品。这是Builder和其他创建模式的不同之处。
Director使用Builder对象构建产品。通常,客户端通过构造方法参数传给director一个builder实例。Director使用这个单一的builder对象完成所有的进一步构建。但是也有替代的方式,把builder传递给director的主要生产方法。
注意,Builder不需要创建一了director类。当你有几个需要不同构建流程的产品变种时,用不同的director类是很方便的。Director把代码都封装在一个单独的类中。
伪代码
这个例子展示了Builder是如何一步一步的构造出汽车。Director类使用不同的构建步骤生产不同类型的汽车。Director使用给他们的build对象工作。这种方式允许你重用已经存在的代码来生产不同种类汽车的用户手册。为了达到这个目的,你只需要一个新的builder类。
因此,Builder模式允许你只通过简单的改变构造步骤的数量和顺序就可以创建产品的不同变种。另外,通过提交构建器对象的替代版本,您可以使用相同的构造代码生成完全不同类型的产品。
1 | // Builder can create different products using the same building process. |
适用性
当你有一个“伸缩”的构造方法。
调用一个有许多可选参数的构造方法很不方便。你必须指定所有的参数,即使你不需要。
解决这个痛点,可以重载一个长的构造方法,创建几个参数较少的构造方法。它们仍然调用主构造方法,只是把省略的参数传入了默认值。
Builder模式允许一步一步构建对象。在构建一个简单对象时,你可以只使用必要的步骤,跳过可选步骤。
当你的代码必须要创建一个产品的不同表示时(比如,石头和木头房子)。产品的构造步骤一样,但细节不同。另外,尽管这个产品是相似的,它们没有必要必须遵循一个通用基类或者接口。
Builder可以用相同的过程构建不同的产品。
不同的产品被不同的builder类表示。控制构建顺序的代码应该放在一个单独的director类中。
当你必须构建一个Composite树或者其他复杂对象时。
Builder一步一步的构建产品。他循序延时甚至递归构建,这在使用树结构时很有用。Builder在运行构建步骤时不能导出位完成的产品。这样可以阻止客户端代码拿到损坏的结果。
如何实现
确保你有构建产品的共同步骤和导致创建各种产品代表的步骤的变化(?)。
创建Builder接口并声明生产步骤。
为每一个产品表示创建Concrete Builder类。并实现它们的创建步骤。
考虑创建一个Director类。他的方法用相同的builder实例,不同的步骤来创建不同的产品配置。
客户端代码创建Builder和Director对象。它首先床架你个builder实例,然后把它传给director的构造方法或者他的生产方法。
客户端应该调用Director对象的生产方法开始构建过程。
是有在所有产品遵循一个通用接口时可以从Director对象中获取构建结果。相反,每个Builder必须有他自己检索结果的方法。
优点
允许一步一步构建产品。
允许使用相同的代码构建不同产品。
把产品的核心业务逻辑与复杂的构建代码隔离。
缺点
- 创建多个额外类增加了代码的复杂度。
和其他模式的关系
通常,设计从使用Factory Method开始(比较简单,并且可以通过子类实现定制),逐渐演变到Abstract Factory,Prototype,或者Builder(更加复杂,但更灵活),因为设计者发现它们需要更灵活的程序。
Builder关注点在一步一步的构造出一个复杂对象。Abstract Factory创建产品对象的系列(不管是简单的还是复杂的)。Builder在最后一步返回产品,但是Abstrct Factory立刻返回结果。
Builder可以一步一步的构建一个复杂的Composite树。
Builder可已才有Bridge结构模式:Director当作接口,Builder当作实现。
Abstract Factory,Builder和Prototype都可以实现为Singleton。
总结
Builder是创建型模式的一种,允许你一步一步构造出来复杂的对象。
不想其他的创建型模式,Builder不需要产品遵循一个通用的接口。他可以使用相同的构造过程生成不同的产品。
用例:Builder模式在你需要创建一个有很多可选配置的对象相当有用。
Builder在Java核心库中也有广泛应用:
java.lang.StringBuilder#append() (unsynchronized)
java.lang.StringBuffer#append() (synchronized)
java.nio.ByteBuffer#put() (also in CharBuffer, ShortBuffer, IntBuffer, LongBuffer, FloatBuffer and DoubleBuffer)
所有java.lang.Appendable接口的实现
鉴定:Builder模式可以在类中识别,它有一个创建方法和几个方法来配置结果对象。Builder方法通常是支持链式调用(比如,someBuilder->setValueA(1)->setValueB(2)->create())。