建造者模式
本课讲解如何在不耦合构建与表示的情况下,表示和构造复杂对象。
什么是建造者模式?
顾名思义,建造者模式用于构建对象。有时我们创建的对象可能非常复杂,由多个子对象组成,或需要经过复杂的构造过程。使用建造者模式可以简化这类复杂对象的创建过程。通常情况下,建造者构建的是一个组合对象(composite)或聚合对象(aggregate)。
正式地说,建造者模式封装或隐藏了复杂对象的构建过程,并将对象的表示与构建过程分离。这种分离使得我们可以使用相同的构建过程来创建不同的对象表示。在 Java 中,“不同的表示”意味着可以创建不同类的对象,而这些对象可能共享相同的构建流程。
类图
类图包含以下实体:
- Builder(建造者)
- Concrete Builder(具体建造者)
- Director(指挥者)
- Product(产品)

示例
继续以飞机为例,假设一架飞机的建造过程按顺序包含以下三个步骤:
- 制造驾驶舱
- 安装发动机
- 安装机翼
在这个假设场景中,所有飞机都至少需要以上三个步骤。然而,客机还可以额外增加一个步骤:安装卫生间。这些步骤对应我们定义中的“构建过程”。最终的产品是“飞机”,但可以有不同的“表示”,例如 F-16 战斗机或波音 747 客机。通过相同的构建过程,我们应该能够生产出 F-16 和波音 747 这两种飞机。
现在来看代码实现。首先定义 AircraftBuilder 类的抽象接口。建造者为最终产品可能包含的每个组件提供一个方法。具体建造者会根据其负责构建的产品变体是否包含该部件,选择性地重写这些方法。
1public abstract class AircraftBuilder { 2 3 public void buildEngine() { 4 5 } 6 7 public void buildWings() { 8 9 } 10 11 public void buildCockpit() { 12 13 } 14 15 public void buildBathrooms() { 16 17 } 18 19 abstract public IAircraft getResult(); 20}
接下来实现两个具体建造者:一个用于 F-16,另一个用于波音 747。
1public class Boeing747Builder extends AircraftBuilder { 2 3 Boeing747 boeing747; 4 5 @Override 6 public void buildCockpit() { 7 8 } 9 10 @Override 11 public void buildEngine() { 12 13 } 14 15 @Override 16 public void buildBathrooms() { 17 18 } 19 20 @Override 21 public void buildWings() { 22 23 } 24 25 public IAircraft getResult() { 26 return boeing747; 27 } 28} 29 30public class F16Builder extends AircraftBuilder { 31 32 F16 f16; 33 34 @Override 35 public void buildEngine() { 36 // 为 F-16 安装发动机 37 // f16.engine = new F16Engine(); 38 } 39 40 @Override 41 public void buildWings() { 42 // 为 F-16 安装机翼 43 // f16.wings = new F16Wings(); 44 } 45 46 @Override 47 public void buildCockpit() { 48 f16 = new F16(); 49 // 为 F-16 安装驾驶舱 50 // f16.cockpit = new F16Cockpit(); 51 } 52 53 public IAircraft getResult() { 54 return f16; 55 } 56}
为了简洁起见,这里只提供了建造者的骨架代码,省略了各个方法的具体实现。注意 F16Builder 并未重写 buildBathrooms 方法,因为 F-16 战斗机没有卫生间;而波音 747 的建造者则重写了该方法,因为客机需要为乘客配备卫生间。
构建飞机所需的流程或算法——即各个部件创建的特定顺序——由另一个名为 Director(指挥者)的类来控制。从某种意义上说,指挥者“指导”着飞机的构建过程,但最终产品仍由建造者返回。
1public class Director { 2 3 AircraftBuilder aircraftBuilder; 4 5 public Director(AircraftBuilder aircraftBuilder) { 6 this.aircraftBuilder = aircraftBuilder; 7 } 8 9 public void construct(boolean isPassenger) { 10 aircraftBuilder.buildCockpit(); 11 aircraftBuilder.buildEngine(); 12 aircraftBuilder.buildWings(); 13 14 if (isPassenger) 15 aircraftBuilder.buildBathrooms(); 16 } 17}
可以看到,我们可以传入任意类型的建造者,从而灵活地生成不同类型(表示)的飞机产品,如 F-16 或波音 747。在本例中,所有建造者返回的是同一父类型对象,但如果所构建的产品差异较大,也可能返回不同的类型。
客户端使用该模式的方式如下:
1public class Client { 2 3 public void main() { 4 5 F16Builder f16Builder = new F16Builder(); 6 Director director = new Director(f16Builder); 7 director.construct(false); 8 9 IAircraft f16 = f16Builder.getResult(); 10 } 11}
AircraftBuilder 接口隐藏了飞机具体是如何被构建的细节。客户端无需了解 F16Engine、F16Cockpit 等具体类,也不需要知道波音 747 相关的实现类。
省略指挥者
你可能会看到建造者模式在不使用 Director(指挥者)的情况下被使用。客户端可以直接实例化建造者,并调用所需的方法来自行构建产品。这种方式常用于解决telescoping constructors(伸缩构造器)问题。想象一个类有很多属性,其中部分属性是可选设置的。此时可以通过建造者仅设置必要的属性并创建对象,避免大量构造函数重载。
其他示例
- Java API 提供了
StringBuilder类,它并不完全符合 GoF 建造者模式的严格定义,但仍可视为建造者模式的一种应用。通过StringBuilder实例和append方法,我们可以逐步构建一个字符串。 - 另一个假设的例子是生成 PDF 或 HTML 格式的文档。考虑以下代码片段:
1 public IDocument construct(DocumentBuilder documentBuilder) { 2 3 documentBuilder.addTitle("为什么要使用设计模式"); 4 documentBuilder.addBody("blah blah blah... more blah blah blah"); 5 documentBuilder.addAuthor("C. H. Afzal"); 6 documentBuilder.addConclusion("Happy Coding!"); 7 8 // 返回文档,根据 DocumentBuilder 的具体实现, 9 // 可能返回 PDF 或 HTML 格式的文档。 10 return documentBuilder.buildDocument(); 11 12 }
上述方法可以出现在指挥者或客户端代码中。通过传入不同类型的 DocumentBuilder 实现,可以构建出不同格式的文档。例如,可以让 HtmlDocumentBuilder 和 PdfDocumentBuilder 继承自抽象类 DocumentBuilder。
注意事项
- 建造者模式看起来可能与抽象工厂模式相似,但关键区别在于:建造者模式是逐步创建对象,而抽象工厂模式则是一次性返回完整对象。