Logo
Section 1 / 5

建造者模式

本课讲解如何在不耦合构建与表示的情况下,表示和构造复杂对象。

导入

建造者模式

本课讲解如何在不耦合构建与表示的情况下,表示和构造复杂对象。

什么是建造者模式?

顾名思义,建造者模式用于构建对象。有时我们创建的对象可能非常复杂,由多个子对象组成,或需要经过复杂的构造过程。使用建造者模式可以简化这类复杂对象的创建过程。通常情况下,建造者构建的是一个组合对象(composite)或聚合对象(aggregate)。

正式地说,建造者模式封装或隐藏了复杂对象的构建过程,并将对象的表示与构建过程分离。这种分离使得我们可以使用相同的构建过程来创建不同的对象表示。在 Java 中,“不同的表示”意味着可以创建不同类的对象,而这些对象可能共享相同的构建流程。


类图

类图包含以下实体:

  • Builder(建造者)
  • Concrete Builder(具体建造者)
  • Director(指挥者)
  • Product(产品)
类图
类图

示例

继续以飞机为例,假设一架飞机的建造过程按顺序包含以下三个步骤:

  1. 制造驾驶舱
  2. 安装发动机
  3. 安装机翼

在这个假设场景中,所有飞机都至少需要以上三个步骤。然而,客机还可以额外增加一个步骤:安装卫生间。这些步骤对应我们定义中的“构建过程”。最终的产品是“飞机”,但可以有不同的“表示”,例如 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。


注意事项

  • 建造者模式看起来可能与抽象工厂模式相似,但关键区别在于:建造者模式是逐步创建对象,而抽象工厂模式则是一次性返回完整对象。

开始

单例模式