设计模式之建造者模式

设计模式之建造者模式

意图

建造者模式(Builder)是一种创建型设计模式, 使你能够分步骤创建复杂对象。 该模式允许你使用相同的创建代码生成不同类型和形式的对象。

使用建造者模式,用户就只需要指定需要建造的类型,具体的建造过程和细节并不需要知道。

建造者模式允许修改一个产品的内部表示。

它将构造和表示两块代码隔离开来。

它很好的控制了构建过程。

img

建造者模式流程说明:

  1. 客户端创建 Director 对象并配置它所需要的 Builder 对象。
  2. Director 负责通知 builder 何时建造 product 的部件。
  3. Builder 处理 director 的请求并添加 product 的部件。
  4. 客户端从 builder 处获得 product。

适用场景

  • 使用建造者模式可避免 “重叠构造函数 (telescopic constructor)” 的出现。
  • 当你希望使用代码创建不同形式的产品时, 可使用建造者模式。
  • 使用建造者构造组合树或其他复杂对象。

结构

img

  1. 建造者 (Builder) 接口声明在所有类型建造者中通用的产品构造步骤。
  2. 具体建造者 (Concrete Builders) 提供构造过程的不同实现。 具体建造者也可以构造不遵循通用接口的产品。
  3. 产品 (Products) 是最终生成的对象。 由不同建造者构造的产品无需属于同一类层次结构或接口。
  4. 主管 (Director) 类定义调用构造步骤的顺序, 这样你就可以创建和复用特定的产品配置。
  5. 客户端 (Client) 必须将某个建造者对象与主管类关联。 一般情况下, 你只需通过主管类构造函数的参数进行一次性关联即可。 此后主管类就能使用建造者对象完成后续所有的构造任务。 但在客户端将建造者对象传递给主管类制造方法时还有另一种方式。 在这种情况下, 你在使用主管类生产产品时每次都可以使用不同的建造者。

【Product】产品类,由多个部件构成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Product {
List<String> parts = new ArrayList<String>();

public void AddPart(String part) {
parts.add(part);
}

public void show() {
System.out.println("============== 产品创建 ==============");
for (String part : parts) {
System.out.println(part);
}
}
}

【Builder】

抽象建造者,确定产品由 ABC 三个部件构成,并声明一个得到产品建造后结果的方法 getResult。

1
2
3
4
5
6
interface Builder {
public void buildPartA();
public void buildPartB();
public void buildPartC();
public Product getResult();
}

【ConcreteBuilder】

实现 Builder 接口中的具体方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ConcreteBuilder implements Builder {
private Product product = new Product();

@Override
public void buildPartA() {
product.AddPart("部件A");
}

@Override
public void buildPartB() {
product.AddPart("部件B");
}

@Override
public void buildPartC() {
product.AddPart("部件C");
}

@Override
public Product getResult() {
return product;
}
}

【Director】

指挥者类,指挥建造 Product 的过程(控制构建各部分组件的顺序)。

1
2
3
4
5
6
7
class Director {
public void construct(Builder builder) {
builder.buildPartC();
builder.buildPartA();
builder.buildPartB();
}
}

【客户端】

用户并不需要知道具体的建造过程,只需指定建造 Product 具体类型。

1
2
3
4
5
6
7
8
9
10
public class BuilderPattern {
public static void main(String[] args) {
Director director = new Director();
Builder builder = new ConcreteBuilder();

director.construct(builder);
Product product = builder.getResult();
product.show();
}
}

【输出】

1
2
3
4
============== 产品创建 ==============
部件C
部件A
部件B

伪代码

下面关于建造者模式的例子演示了你可以如何复用相同的对象构造代码来生成不同类型的产品——例如汽车 (Car)——及其相应的使用手册 (Manual)。

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// 只有当产品较为复杂且需要详细配置时,使用建造者模式才有意义。下面的两个
// 产品尽管没有同样的接口,但却相互关联。
class Car is
// 一辆汽车可能配备有 GPS 设备、行车电脑和几个座位。不同型号的汽车(
// 运动型轿车、SUV 和敞篷车)可能会安装或启用不同的功能。

class Manual is
// 用户使用手册应该根据汽车配置进行编制,并介绍汽车的所有功能。


// 建造者接口声明了创建产品对象不同部件的方法。
interface Builder is
method reset()
method setSeats(...)
method setEngine(...)
method setTripComputer(...)
method setGPS(...)

// 具体建造者类将遵循建造者接口并提供生成步骤的具体实现。你的程序中可能会
// 有多个以不同方式实现的建造者变体。
class CarBuilder implements Builder is
private field car:Car

// 一个新的建造者实例必须包含一个在后续组装过程中使用的空产品对象。
constructor CarBuilder() is
this.reset()

// reset(重置)方法可清除正在生成的对象。
method reset() is
this.car = new Car()

// 所有生成步骤都会与同一个产品实例进行交互。
method setSeats(...) is
// 设置汽车座位的数量。

method setEngine(...) is
// 安装指定的引擎。

method setTripComputer(...) is
// 安装行车电脑。

method setGPS(...) is
// 安装全球定位系统。

// 具体建造者需要自行提供获取结果的方法。这是因为不同类型的建造者可能
// 会创建不遵循相同接口的、完全不同的产品。所以也就无法在建造者接口中
// 声明这些方法(至少在静态类型的编程语言中是这样的)。
//
// 通常在建造者实例将结果返回给客户端后,它们应该做好生成另一个产品的
// 准备。因此建造者实例通常会在 `getProduct(获取产品)`方法主体末尾
// 调用重置方法。但是该行为并不是必需的,你也可让建造者等待客户端明确
// 调用重置方法后再去处理之前的结果。
method getProduct():Car is
product = this.car
this.reset()
return product

// 建造者与其他创建型模式的不同之处在于:它让你能创建不遵循相同接口的产品。
class CarManualBuilder implements Builder is
private field manual:Manual

constructor CarManualBuilder() is
this.reset()

method reset() is
this.manual = new Manual()

method setSeats(...) is
// 添加关于汽车座椅功能的文档。

method setEngine(...) is
// 添加关于引擎的介绍。

method setTripComputer(...) is
// 添加关于行车电脑的介绍。

method setGPS(...) is
// 添加关于 GPS 的介绍。

method getProduct():Manual is
// 返回使用手册并重置建造者。


// 主管只负责按照特定顺序执行生成步骤。其在根据特定步骤或配置来生成产品时
// 会很有帮助。由于客户端可以直接控制建造者,所以严格意义上来说,主管类并
// 不是必需的。
class Director is
private field builder:Builder

// 主管可同由客户端代码传递给自身的任何建造者实例进行交互。客户端可通
// 过这种方式改变最新组装完毕的产品的最终类型。
method setBuilder(builder:Builder)
this.builder = builder

// 主管可使用同样的生成步骤创建多个产品变体。
method constructSportsCar(builder: Builder) is
builder.reset()
builder.setSeats(2)
builder.setEngine(new SportEngine())
builder.setTripComputer(true)
builder.setGPS(true)

method constructSUV(builder: Builder) is
// ...


// 客户端代码会创建建造者对象并将其传递给主管,然后执行构造过程。最终结果
// 将需要从建造者对象中获取。
class Application is

method makeCar() is
director = new Director()

CarBuilder builder = new CarBuilder()
director.constructSportsCar(builder)
Car car = builder.getProduct()

CarManualBuilder builder = new CarManualBuilder()
director.constructSportsCar(builder)

// 最终产品通常需要从建造者对象中获取,因为主管不知晓具体建造者和
// 产品的存在,也不会对其产生依赖。
Manual manual = builder.getProduct()

案例

使用示例: 建造者模式是 Java 世界中的一个著名模式。 当你需要创建一个可能有许多配置选项的对象时, 该模式会特别有用。

建造者在 Java 核心程序库中得到了广泛的应用:

识别方法: 建造者模式可以通过类来识别, 它拥有一个构建方法和多个配置结果对象的方法。 建造者方法通常支持方法链 (例如 someBuilder->setValueA(1)->setValueB(2)->create()

与其他模式的关系

参考资料