概述
在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法确相对稳定。如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的 “稳定构建算法”不随着需求改变而改变?这就是要说的建造者模式。
本文通过现实生活中的买KFC的例子,用图解的方式来诠释建造者模式。
意图
将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
模型图

生活中的例子
生成器模式将复杂对象的构建与对象的表现分离开来,这样使得同样的构建过程可以创建出不同的表现。这种模式用于快餐店制作儿童餐。典型的儿童餐包括一个主食,一个辅食,一杯饮料和一个玩具(例如汉堡、炸鸡、可乐和玩具车)。这些在不同的儿童餐中可以是不同的,但是组合成儿童餐的过程是相同的。无论顾客点的是汉堡,三名治还是鸡肉,过程都是一样的。柜台的员工直接把主食,辅食和玩具放在一起。这些是放在一个袋子中的。饮料被倒入杯中,放在袋子外边。这些过程在相互竞争的餐馆中是同样的。

实现过程图解
在这里我们还是以去KFC店买套餐为例子,示意图如下:

客户端:顾客。想去买一套套餐(这里面包括汉堡,可乐,薯条),可以有1号和2号两种套餐供顾客选择。
指导者角色:收银员。知道顾客想要买什么样的套餐,并告诉餐馆员工去准备套餐。
建造者角色:餐馆员工。按照收银员的要求去准备具体的套餐,分别放入汉堡,可乐,薯条等。
产品角色:最后的套餐,所有的东西放在同一个盘子里面。
下面开始我们的买套餐过程。
1.客户创建Derector对象,并用它所想要的Builder对象进行配置。顾客进入KFC店要买套餐,先找到一个收银员,相当于创建了一个指导者对象。这位收银员给出两种套餐供顾客选择:1普通套餐,2黄金套餐。完成的工作如时序图中红色部分所示。

程序实现:
1
using
System;
2
using
System.Configuration;
3
using
System.Reflection;
4

5
namespace
KFC
6

{
7

/**/
///
8
///
Client 类
9
///
10
public
class
Client
11

{
12
public
static
void
Main(
string
[] args)
13

{
14
FoodManager foodmanager
=
new
FoodManager();
15

16
Builder instance;
17

18
Console.WriteLine(
"
Please Enter Food No:
"
);
19

20
string
No
=
Console.ReadLine();
21

22
string
foodType
=
ConfigurationSettings.AppSettings[
"
No
"
+
No];
23

24
instance
=
(Builder)Assembly.Load(
"
KFC
"
).CreateInstance(
"
KFC.
"
+
foodType);
25

26
foodmanager.Construct(instance);
27
}
28
}
29
}
30
产品(套餐)类:
1
using
System;
2
using
System.Collections;
3

4
namespace
KFC
5

{
6

/**/
///
7
///
Food类,即产品类
8
///
9
public
class
Food
10

{
11
Hashtable food
=
new
Hashtable();
12
13

/**/
///
14
///
添加食品
15
///
16
///
食品名称
17
///
价格
18
public
void
Add(
string
strName,
string
Price)
19

{
20
food.Add(strName,Price);
21
}
22
23

/**/
///
24
///
显示食品清单
25
///
26
public
void
Show()
27

{
28
IDictionaryEnumerator myEnumerator
=
food.GetEnumerator();
29
Console.WriteLine(
"
Food List:
"
);
30
Console.WriteLine(
"
------------------------------
"
);
31
string
strfoodlist
=
""
;
32
while
(myEnumerator.MoveNext())
33

{
34
strfoodlist
=
strfoodlist
+
"
\n\n
"
+
myEnumerator.Key.ToString();
35
strfoodlist
=
strfoodlist
+
"
:\t
"
+
myEnumerator.Value.ToString();
36
}
37
Console.WriteLine(strfoodlist);
38
Console.WriteLine(
"
\n------------------------------
"
);
39
}
40
}
41
}
42
2.指导者通知建造器。收银员(指导者)告知餐馆员工准备套餐。这里我们准备套餐的顺序是:放入汉堡,可乐倒入杯中,薯条放入盒中,并把这些东西都放在盘子上。这个过程对于普通套餐和黄金套餐来说都是一样的,不同的是它们的汉堡,可乐,薯条价格不同而已。如时序图红色部分所示:

程序实现:
1
using
System;
2

3
namespace
KFC
4

{
5

/**/
///
6
///
FoodManager类,即指导者
7
///
8
public
class
FoodManager
9

{
10
public
void
Construct(Builder builder)
11

{
12
builder.BuildHamb();
13

14
builder.BuildCoke();
15

16
builder.BuildChip();
17
}
18
}
19
}
20
3.建造者处理指导者的要求,并将部件添加到产品中。餐馆员工(建造者)按照收银员要求的把对应的汉堡,可乐,薯条放入盘子中。这部分是建造者模式里面富于变化的部分,因为顾客选择的套餐不同,套餐的组装过程也不同,这步完成产品对象的创建工作。
程序实现:
1
using
System;
2

3
namespace
KFC
4

{
5

/**/
///
6
///
Builder类,即抽象建造者类,构造套餐
7
///
8
public
abstract
class
Builder
9

{
10

/**/
///
11
///
添加汉堡
12
///
13
public
abstract
void
BuildHamb();
14
15

/**/
///
16
///
添加可乐
17
///
18
public
abstract
void
BuildCoke();
19
20

/**/
///
21
///
添加薯条
22
///
23
public
abstract
void
BuildChip();
24
25

/**/
///
26
///
返回结果
27
///
28
///
食品对象
29
public
abstract
Food GetFood();
30
}
31
}
32
1
using
System;
2

3
namespace
KFC
4

{
5

/**/
///
6
///
NormalBuilder类,具体构造者,普通套餐
7
///
8
public
class
NormalBuilder:Builder
9

{
10
private
Food NormalFood
=
new
Food();
11

12
public
override
void
BuildHamb()
13

{
14
NormalFood.Add(
"
NormalHamb
"
,
"
¥10.50
"
);
15
}
16
17
public
override
void
BuildCoke()
18

{
19
NormalFood.Add(
"
CokeCole
"
,
"
¥4.50
"
);
20
}
21

22
public
override
void
BuildChip()
23

{
24
NormalFood.Add(
"
FireChips
"
,
"
¥2.00
"
);
25
}
26

27
public
override
Food GetFood()
28

{
29
return
NormalFood;
30
}
31

32
}
33
}
34
1
using
System;
2

3
namespace
KFC
4

{
5

/**/
///
6
///
GoldBuilder类,具体构造者,黄金套餐
7
///
8
public
class
GoldBuilder:Builder
9

{
10
private
Food GoldFood
=
new
Food();
11

12
public
override
void
BuildHamb()
13

{
14
GoldFood.Add(
"
GoldHamb
"
,
"
¥13.50
"
);
15
}
16
17
public
override
void
BuildCoke()
18

{
19
GoldFood.Add(
"
CokeCole
"
,
"
¥4.50
"
);
20
}
21

22
public
override
void
BuildChip()
23

{
24
GoldFood.Add(
"
FireChips
"
,
"
¥3.50
"
);
25
}
26

27
public
override
Food GetFood()
28

{
29
return
GoldFood;
30
}
31

32
}
33
}
34
4.客户从建造者检索产品。从餐馆员工准备好套餐后,顾客再从餐馆员工那儿拿回套餐。这步客户程序要做的仅仅是取回已经生成的产品对象,如时序图中红色部分所示。

完整的客户程序:
1
using
System;
2
using
System.Configuration;
3
using
System.Reflection;
4

5
namespace
KFC
6

{
7

/**/
///
8
///
Client 类
9
///
10
public
class
Client
11

{
12
public
static
void
Main(
string
[] args)
13

{
14
FoodManager foodmanager
=
new
FoodManager();
15

16
Builder instance;
17

18
Console.WriteLine(
"
Please Enter Food No:
"
);
19

20
string
No
=
Console.ReadLine();
21

22
string
foodType
=
ConfigurationSettings.AppSettings[
"
No
"
+
No];
23

24
instance
=
(Builder)Assembly.Load(
"
KFC
"
).CreateInstance(
"
KFC.
"
+
foodType);
25

26
foodmanager.Construct(instance);
27

28
Food food
=
instance.GetFood();
29
food.Show();
30

31
Console.ReadLine();
32
}
33
}
34
}
35
通过分析不难看出,在这个例子中,在准备套餐的过程是稳定的,即按照一定的步骤去做,而套餐的组成部分则是变化的,有可能是普通套餐或黄金套餐等。这个变化就是建造者模式中的“变化点“,就是我们要封装的部分。
另外一个例子
在这里我们再给出另外一个关于建造房子的例子。客户程序通过调用指导者 (CDirector class)的BuildHouse()方法来创建一个房子。该方法有一个布尔型的参数blnBackyard,当blnBackyard为假时指导者将创建一个Apartment(Concrete Builder),当它为真时将创建一个Single Family Home(Concrete Builder)。这两种房子都实现了接口Ihouse。
程序实现:
1
//
关于建造房屋的例子
2
using
System;
3
using
System.Collections;
4

5

/**/
///
6
///
抽象建造者
7
///
8
public
interface
IHouse
9

{
10
bool
GetBackyard();
11
long
NoOfRooms();
12
string
Description();
13
}
14

15

/**/
///
16
///
具体建造者
17
///
18
public
class
CApt:IHouse
19

{
20
private
bool
mblnBackyard;
21
private
Hashtable Rooms;
22
public
CApt()
23

{
24
CRoom room;
25
Rooms
=
new
Hashtable();
26
room
=
new
CRoom();
27
room.RoomName
=
"
Master Bedroom
"
;
28
Rooms.Add (
"
room1
"
,room);
29

30
room
=
new
CRoom();
31
room.RoomName
=
"
Second Bedroom
"
;
32
Rooms.Add (
"
room2
"
,room);
33

34
room
=
new
CRoom();
35
room.RoomName
=
"
Living Room
"
;
36
Rooms.Add (
"
room3
"
,room);
37
38
mblnBackyard
=
false
;
39
}
40

41
public
bool
GetBackyard()
42

{
43
return
mblnBackyard;
44
}
45
public
long
NoOfRooms()
46

{
47
return
Rooms.Count;
48
}
49
public
string
Description()
50

{
51
IDictionaryEnumerator myEnumerator
=
Rooms.GetEnumerator();
52
string
strDescription;
53
strDescription
=
"
This is an Apartment with
"
+
Rooms.Count
+
"
Rooms \n
"
;
54
strDescription
=
strDescription
+
"
This Apartment doesn't have a backyard \n
"
;
55
while
(myEnumerator.MoveNext())
56

{
57
strDescription
=
strDescription
+
"
\n
"
+
myEnumerator.Key
+
"
\t
"
+
((CRoom)myEnumerator.Value).RoomName;
58
}
59
return
strDescription;
60
}
61
}
62

63

/**/
///
64
///
具体建造者
65
///
66
public
class
CSFH:IHouse
67

{
68
private
bool
mblnBackyard;
69
private
Hashtable Rooms;
70
public
CSFH()
71

{
72
CRoom room;
73
Rooms
=
new
Hashtable();
74

75
room
=
new
CRoom();
76
room.RoomName
=
"
Master Bedroom
"
;
77
Rooms.Add (
"
room1
"
,room);
78

79
room
=
new
CRoom();
80
room.RoomName
=
"
Second Bedroom
"
;
81
Rooms.Add (
"
room2
"
,room);
82

83
room
=
new
CRoom();
84
room.RoomName
=
"
Third Room
"
;
85
Rooms.Add (
"
room3
"
,room);
86
87
room
=
new
CRoom();
88
room.RoomName
=
"
Living Room
"
;
89
Rooms.Add (
"
room4
"
,room);
90

91
room
=
new
CRoom();
92
room.RoomName
=
"
Guest Room
"
;
93
Rooms.Add (
"
room5
"
,room);
94

95
mblnBackyard
=
true
;
96
97
}
98

99
public
bool
GetBackyard()
100

{
101
return
mblnBackyard;
102
}
103
public
long
NoOfRooms()
104

{
105
return
Rooms.Count;
106
}
107
public
string
Description()
108

{
109
IDictionaryEnumerator myEnumerator
=
Rooms.GetEnumerator();
110
string
strDescription;
111
strDescription
=
"
This is an Single Family Home with
"
+
Rooms.Count
+
"
Rooms \n
"
;
112
strDescription
=
strDescription
+
"
This house has a backyard \n
"
;
113
while
(myEnumerator.MoveNext())
114

{
115
strDescription
=
strDescription
+
"
\n
"
+
myEnumerator.Key
+
"
\t
"
+
((CRoom)myEnumerator.Value).RoomName;
116
}
117
return
strDescription;
118
}
119
}
120

121
public
interface
IRoom
122

{
123

string
RoomName
{
get
;
set
;}
124
}
125

126
public
class
CRoom:IRoom
127

{
128
private
string
mstrRoomName;
129
public
string
RoomName
130

{
131
get
132

{
133
return
mstrRoomName;
134
}
135
set
136

{
137
mstrRoomName
=
value;
138
}
139
}
140
}
141

142

/**/
///
143
///
指导者
144
///
145
public
class
CDirector
146

{
147
public
IHouse BuildHouse(
bool
blnBackyard)
148

{
149
if
(blnBackyard)
150

{
151
return
new
CSFH();
152
}
153
else
154

{
155
return
new
CApt();
156
}
157
}
158
}
159

160

/**/
///
161
///
客户程序
162
///
163
public
class
Client
164

{
165
static
void
Main(
string
[] args)
166

{
167
CDirector objDirector
=
new
CDirector();
168
IHouse objHouse;
169

170
string
Input
=
Console.ReadLine();
171
objHouse
=
objDirector.BuildHouse(
bool
.Parse(Input));
172
173
Console.WriteLine(objHouse.Description());
174
Console.ReadLine();
175
}
176
}
177

178
建造者模式的几种演化
省略抽象建造者角色
系统中只需要一个具体建造者,省略掉抽象建造者,结构图如下:

指导者代码如下:
1
class
Director
2

{
3
private
ConcreteBuilder builder;
4
5
public
void
Construct()
6

{
7
builder.BuildPartA();
8
builder.BuildPartB();
9
}
10
}
省略指导者角色
抽象建造者角色已经被省略掉,还可以省略掉指导者角色。让Builder角色自己扮演指导者与建造者双重角色。结构图如下:

建造者角色代码如下:
1
public
class
Builder
2

{
3
private
Product product
=
new
Product();
4
5
public
void
BuildPartA()
6

{
7
//
...
8
}
9
10
public
void
BuildPartB()
11

{
12
//
...
13
}
14
15
public
Product GetResult()
16

{
17
return
product;
18
}
19
20
public
void
Construct()
21

{
22
BuildPartA();
23
BuildPartB();
24
}
25
}
客户程序:
1
public
class
Client
2

{
3
private
static
Builder builder;
4
5
public
static
void
Main()
6

{
7
builder
=
new
Builder();
8
builder.Construct();
9
Product product
=
builder.GetResult();
10
}
11
}
合并建造者角色和产品角色
建造模式失去抽象建造者角色和指导者角色后,可以进一步退化,从而失去具体建造者角色,此时具体建造者角色和产品角色合并,从而使得产品自己就是自己的建造者。这样做混淆了对象的建造者和对象本身,但是有时候一个产品对象有着固定的几个零件,而且永远只有这几个零件,此时将产品类和建造类合并,可以使系统简单易读。结构图如下:

实现要点
1、建造者模式主要用于“分步骤构建一个复杂的对象”,在这其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化。
2、产品不需要抽象类,特别是由于创建对象的算法复杂而导致使用此模式的情况下或者此模式应用于产品的生成过程,其最终结果可能差异很大,不大可能提炼出一个抽象产品类。
3、创建者中的创建子部件的接口方法不是抽象方法而是空方法,不进行任何操作,具体的创建者只需要覆盖需要的方法就可以,但是这也不是绝对的,特别是类似文本转换这种情况下,缺省的方法将输入原封不动的输出是合理的缺省操作。
4、前面我们说过的抽象工厂模式(Abtract Factory)解决“系列对象”的需求变化,Builder模式解决“对象部分”的需求变化,建造者模式常和组合模式(Composite Pattern)结合使用。
效果
1、建造者模式的使用使得产品的内部表象可以独立的变化。使用建造者模式可以使客户端不必知道产品内部组成的细节。
2、每一个Builder都相对独立,而与其它的Builder无关。
3、可使对构造过程更加精细控制。
4、将构建代码和表示代码分开。
5、建造者模式的缺点在于难于应付“分步骤构建算法”的需求变动。
适用性
以下情况应当使用建造者模式:
1、需要生成的产品对象有复杂的内部结构。
2、需要生成的产品对象的属性相互依赖,建造者模式可以强迫生成顺序。
3、 在对象创建过程中会使用到系统中的一些其它对象,这些对象在产品对象的创建过程中不易得到。
应用场景
1、RTF文档交换格式阅读器。
2、.NET环境下的字符串处理StringBuilder,这是一种简化了的建造者模式。
3、 ……
总结
建造者模式的实质是解耦组装过程和创建具体部件,使得我们不用去关心每个部件是如何组装的。
源程序下载:/Files/Terrylee/BuilderPattern.rar