建造者模式

来源:互联网 发布:鲲鹏网络传媒有限公司 编辑:程序博客网 时间:2024/05/16 17:58

原文链接:http://www.cnblogs.com/cbf4life/archive/2010/01/14/1647710.html

11.1 变化是永恒的

      又是一个周三,快要下班了,老大突然拉住我,喜滋滋地告诉我:“牛叉公司很满意我们做的模型,又签订了一个合同,把奔驰、宝马的车辆模型都交给我们公司制作了,不过这次又额外增加了一个新需求:汽车的启动、停止、喇叭声音、引擎声音都由客户自己控制,他想什么顺序就什么顺序,这个没问题吧?”

      看着老大殷切的目光,我还能说啥?非常肯定地点头,“没问题!”,加班加点做呗,“再苦再累就当自己二百五!再难再险就当自己二皮脸!与君共勉!”这句话说出了俺的心声。

      那任务是接下来,又是一个时间紧,工程量大的项目,为什么是“又”呢?因为基本上每个项目都是如此,我该怎么来完成这个任务呢?

      首先我们分析一下需求,奔驰、宝马都是一个产品,他们有共有的属性,牛叉公司关心的是单个模型的运行过程:奔驰模型A是先有引擎声音,然后再响喇叭;奔驰模型B是先启动起来,然后再有引擎声音,这才是牛叉公司要关心的,那到我们老大这边呢,就是满足人家的要求,要什么顺序就立马能产生什么顺序的模型出来,我就负责把老大的要求实现出来,而且还要是批量的,也就是说牛叉公司下单订购宝马A车模,我们老大马上就找我“生产一个这样的车模,启动完毕后,喇叭响一下”,然后我们就准备开始批量生产这些模型。由我生产出N多个奔驰和宝马车辆模型,这些车辆模型的都有run()方法,但是具体到每一个模型的run()方法中间的执行任务的顺序是不同的,老大说要啥顺序,我就给啥顺序,最终客户买走后只能是既定的模型。好,需求还是比较复杂,我们先一个一个的解决,先从找一个最简单的切入点——产品类,每个车都是一个产品,如图11-1所示。

clip_image002

图11-1 汽车模型类图

      类图比较简单,在CarModel中我们定义了一个setSequence方法,车辆模型的这几个动作要如何排布,是在这个ArrayList中定义的,然后run()方法根据sequence定义的顺序完成指定的顺序动作,与我们上一章节介绍的模板方法模式是不是非常类似?好,我们先看CarModel源代码,如代码清单11-1所示。

代码清单11-1 车辆模型的抽象类

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
public abstract class CarModel {
 
//这个参数是各个基本方法执行的顺序
 
privateArrayList<String> sequence =newArrayList<String>();
 
//模型是启动开始跑了
 
protectedabstractvoidstart();
 
//能发动,那还要能停下来,那才是真本事
 
protectedabstractvoidstop();
 
//喇叭会出声音,是滴滴叫,还是哔哔叫
 
protectedabstractvoidalarm();
 
//引擎会轰隆隆地响,不响那是假的
 
protectedabstractvoidengineBoom();
 
//那模型应该会跑吧,别管是人推的,还是电力驱动,总之要会跑
 
final publicvoidrun() {
 
//循环一边,谁在前,就先执行谁
 
for(inti=0;i<this.sequence.size();i++){
 
String actionName =this.sequence.get(i);
 
if(actionName.equalsIgnoreCase("start")){
 
this.start();//开启汽车
 
}elseif(actionName.equalsIgnoreCase("stop")){
 
this.stop();//停止汽车
 
}elseif(actionName.equalsIgnoreCase("alarm")){
 
this.alarm();//喇叭开始叫了
 
}elseif(actionName.equalsIgnoreCase("engine boom")){//如果是engine boom关键字
 
this.engineBoom();//引擎开始轰鸣
 
}
 
}
 
}
 
//把传递过来的值传递到类内
 
final publicvoidsetSequence(ArrayList<String> sequence){
 
this.sequence = sequence;
 
}
 
}

CarModel的设计原理是这样的,setSequence方法是允许客户自己设置一个顺序,是要先启动响一下喇叭再跑起来,还是要先响一下喇叭再启动,对于一个具体的模型永远都固定的,但是对N多个模型就是动态的了。在子类中实现父类的基本方法,run()方法读取sequence,然后遍历sequence中的字符串,哪个字符串在先,就先执行哪个方法。

两个实现类分别实现父类的基本方法,奔驰模型如代码清单11-2所示。

代码清单11-2 奔驰模型代码

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
public class BenzModel extends CarModel {
 
protectedvoidalarm() {
 
System.out.println("奔驰车的喇叭声音是这个样子的...");
 
}
 
protectedvoidengineBoom() {
 
System.out.println("奔驰车的引擎室这个声音的...");
 
}
 
protectedvoidstart() {
 
System.out.println("奔驰车跑起来是这个样子的...");
 
}
 
protectedvoidstop() {
 
System.out.println("奔驰车应该这样停车...");
 
}
 
}

      宝马车模型如代码清单11-3所示。

代码清单11-3 宝马模型代码

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
public class BMWModel extends CarModel {
 
protectedvoidalarm() {
 
System.out.println("宝马车的喇叭声音是这个样子的...");
 
}
 
protectedvoidengineBoom() {
 
System.out.println("宝马车的引擎室这个声音的...");
 
}
 
protectedvoidstart() {
 
System.out.println("宝马车跑起来是这个样子的...");
 
}
 
protectedvoidstop() {
 
System.out.println("宝马车应该这样停车...");
 
}
 
}

      两个产品的实现类都完成,我们来模拟一下牛叉公司的要求:生产1件奔驰模型,要求跑的时候,先发动引擎,然后再挂档启动,然后停下来,不需要喇叭。这个需求很容易满足,我们增加一个场景类实现该需求,如代码清单11-4所示。

代码清单11-4 宝马模型代码

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
public class Client {
 
public static void main(String[] args) {
 
/*
 
* 客户告诉牛叉公司,我要这样一个模型,然后牛叉公司就告诉我老大
 
* 说要这样一个模型,这样一个顺序,然后我就来制造
 
*/
 
BenzModel benz =newBenzModel();
 
//存放run的顺序
 
ArrayList<String> sequence =newArrayList<String>();
 
sequence.add("engine boom");//客户要求,run的时候时候先发动引擎
 
sequence.add("start");//启动起来
 
sequence.add("stop");//开了一段就停下来
 
//我们把这个顺序赋予奔驰车
 
benz.setSequence(sequence);
 
benz.run();
 
}
 
}

      运行结果如下所示。

奔驰车的引擎是这个声音的...

奔驰车跑起来是这个样子的...

奔驰车应该这样停车...

      看,我们组装了这样的一辆汽车,满足了牛叉公司的需求了。但是想想我们的需求,汽车的动作执行顺序是要能够随意调整的,我们只满足了一个需求,还要下一个需求呀,然后是第2件宝马模型,只要启动、停止,其他的什么都不要,第3件模型,先喇叭,然后启动,然后停止,第4件...直到把你逼疯为止,那怎么办?我们就一个一个的来写场景类满足吗?不可能了,那我们要想办法来解决这个问题,有了!我们为每种模型产品模型定义一个建造者,你要啥顺序直接告诉建造者,由建造者来建造,于是乎我们就有了如图11-2所示的类图。

clip_image004

图11-2 增加了建造者的汽车模型类图

      增加了一个CarBuilder抽象类,由它来组装各个车模,要什么类型什么顺序的车辆模型,都由相关的子类完成,首先编写CarBuilder代码,如代码清单11-5所示。

代码清单11-5 抽象汽车组装者

1
2
3
4
5
6
7
8
9
10
11
public abstract class CarBuilder {
 
//建造一个模型,你要给我一个顺序要,就是组装顺序
 
public abstract void setSequence(ArrayList<String> sequence);
 
//设置完毕顺序后,就可以直接拿到这个车辆模型
 
public abstract CarModel getCarModel();
 
}

      很简单,每个车辆模型都要有确定的运行顺序,然后才能返回一个车辆模型。奔驰车的组装者如代码清单11-6所示。

代码清单11-6 奔驰车组装者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class BenzBuilder extends CarBuilder {
 
privateBenzModel benz =newBenzModel();
 
public CarModel getCarModel() {
 
return this.benz;
 
}
 
publicvoidsetSequence(ArrayList<String> sequence) {
 
this.benz.setSequence(sequence);
 
}
 
}

      非常简单实用的程序,给定一个汽车的运行顺序,然后就返回一个奔驰车,简单了很多,宝马车的组装与此相同,如代码清单11-7所示。

代码清单11-7 宝马车组装者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class BMWBuilder extends CarBuilder {
 
privateBMWModel bmw =newBMWModel();
 
public CarModel getCarModel() {
 
return this.bmw;
 
}
 
publicvoidsetSequence(ArrayList<String> sequence) {
 
this.bmw.setSequence(sequence);
 
}
 
}

      两个组装者都完成了,我们再来看看牛叉公司的需求如何满足,修改一下场景类,如代码清单11-8所示。

代码清单11-8 修改后的场景类

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
public class Client {
 
public static void main(String[] args) {
 
/*
 
* 客户告诉牛叉公司,我要这样一个模型,然后牛叉公司就告诉我老大
 
* 说要这样一个模型,这样一个顺序,然后我就来制造
 
*/
 
ArrayList<String> sequence =newArrayList<String>();//存放run的顺序
 
sequence.add("engine boom");//客户要求,run的时候时候先发动引擎
 
sequence.add("start");//启动起来
 
sequence.add("stop");//开了一段就停下来
 
//要一个奔驰车:
 
BenzBuilder benzBuilder =newBenzBuilder();
 
//把顺序给这个builder类,制造出这样一个车出来
 
benzBuilder.setSequence(sequence);
 
//制造出一个奔驰车
 
BenzModel benz = (BenzModel)benzBuilder.getCarModel();
 
//奔驰车跑一下看看
 
benz.run();
 
}
 
}

      运行结果如下所示。

奔驰车的引擎是这个声音的...

奔驰车跑起来是这个样子的...

奔驰车应该这样停车...

      那如果我再想要个同样顺序的宝马车呢?很简单,再次修改一下场景类,如代码清单11-9所示。

代码清单11-9 相同顺序的宝马车的场景类

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
public class Client {
 
public static void main(String[] args) {
 
ArrayList<String> sequence =newArrayList<String>();//存放run的顺序
 
sequence.add("engine boom");//客户要求,run的时候时候先发动引擎
 
sequence.add("start");//启动起来
 
sequence.add("stop");//开了一段就挺下来
 
//要一个奔驰车:
 
BenzBuilder benzBuilder =newBenzBuilder();
 
//把顺序给这个builder类,制造出这样一个车出来
 
benzBuilder.setSequence(sequence);
 
//制造出一个奔驰车
 
BenzModel benz = (BenzModel)benzBuilder.getCarModel();
 
//奔驰车跑一下看看
 
benz.run();
 
//按照同样的顺序,我再要一个宝马
 
BMWBuilder bmwBuilder =newBMWBuilder();
 
bmwBuilder.setSequence(sequence);
 
BMWModel bmw = (BMWModel)bmwBuilder.getCarModel();
 
bmw.run();
 
}
 
}

      运行结果如下所示。

奔驰车的引擎是这个声音的...

奔驰车跑起来是这个样子的...

奔驰车应该这样停车...

宝马车的引擎是这个声音的...

宝马车跑起来是这个样子的...

宝马车应该这样停车...

      看,同样运行顺序的宝马车也生产出来了,而且代码是不是比刚开始直接访问产品类(Procuct)简单了很多。我们在做项目时,经常会有一个共识:需求是无底洞,是无理性的,不可能你告诉它不增加需求就不增加,这四个过程(start、stop、alarm、engineBoom)按照排列组合有很多种,牛叉公司可以随意组合,它要什么顺序的车模我就必须生成什么顺序的车模,客户可是上帝!那我们不可能预知他们要什么顺序的模型呀,怎么办?封装一下,找一个导演,指挥各个事件的先后顺序,然后为每种顺序指定一个代码,你说一种我们立刻就给你生产处理,好方法,厉害!我们先修正一下类图,如图11-3所示。

clip_image006

图11-3 完整汽车模型类图

      类图看着复杂了,但是还是比较简单,我们增加了一个Director类,负责按照指定的顺序生产模型,其中方法说明如下:

  • getABenzModel方法

      组建出A型号的奔驰车辆模型,其过程为只有启动(start)、停止(stop)方法,其他的引擎声音、喇叭都没有。

  • getBBenzModel方法

      组建出B型号的奔驰车,其过程为先发动引擎(engine boom),然后启动(star),再然后停车(stop),没有喇叭。

  • getCBMWModel方法

      组建出C型号的宝马车,其过程为先喇叭叫一下(alarm),然后(start),再然后是停车(stop),引擎不轰鸣。

  • getDBMWModel方法

      组建出D型号的宝马车,其过程就一个启动(start),然后一路跑到黑,永动机,没有停止方法,没有喇叭,没有引擎轰鸣。

      其他的E型号、F型号……等等,可以有很多,启动(start)、停止(stop)、喇叭(alarm)、引擎轰鸣(engine boom)这四个方法在这个类中可以随意的自由组合,有几种呢?好像是排列组合,这个不会算,高中数学没学好,反正有很多种了,都可以实现。Director类如代码清单11-10所示。

代码清单11-10 导演类

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
public class Director {
 
privateArrayList<String> sequence =newArrayList();
 
privateBenzBuilder benzBuilder =newBenzBuilder();
 
privateBMWBuilder bmwBuilder =newBMWBuilder();
 
/*
 
* A类型的奔驰车模型,先start,然后stop,其他什么引擎了,喇叭一概没有
 
*/
 
publicBenzModel getABenzModel(){
 
//清理场景,这里是一些初级程序员不注意的地方
 
this.sequence.clear();
 
//这只ABenzModel的执行顺序
 
this.sequence.add("start");
 
this.sequence.add("stop");
 
//按照顺序返回一个奔驰车
 
this.benzBuilder.setSequence(this.sequence);
 
return(BenzModel)this.benzBuilder.getCarModel();
 
}
 
/*
 
* B型号的奔驰车模型,是先发动引擎,然后启动,然后停止,没有喇叭
 
*/
 
publicBenzModel getBBenzModel(){
 
this.sequence.clear();
 
this.sequence.add("engine boom");
 
this.sequence.add("start");
 
this.sequence.add("stop");
 
this.benzBuilder.setSequence(this.sequence);
 
return(BenzModel)this.benzBuilder.getCarModel();
 
}
 
/*
 
* C型号的宝马车是先按下喇叭(炫耀嘛),然后启动,然后停止
 
*/
 
publicBMWModel getCBMWModel(){
 
this.sequence.clear();
 
this.sequence.add("alarm");
 
this.sequence.add("start");
 
this.sequence.add("stop");
 
this.bmwBuilder.setSequence(this.sequence);
 
return(BMWModel)this.bmwBuilder.getCarModel();
 
}
 
/*
 
* D类型的宝马车只有一个功能,就是跑,启动起来就跑,永远不停止,牛叉
 
*/
 
publicBMWModel getDBMWModel(){
 
this.sequence.clear();
 
this.sequence.add("start");
 
this.bmwBuilder.setSequence(this.sequence);
 
return(BMWModel)this.benzBuilder.getCarModel();
 
}
 
/*
 
* 这里还可以有很多方法,你可以先停止,然后再启动,或者一直停着不动,静态的嘛
 
* 导演类嘛,按照什么顺序是导演说了算
 
*/
 
}

      顺便说一下,大家看一下程序中有很多this调用,这个我一般是这样要求项目组成员的,如果你要调用类中的成员变量或方法,需要在前面加上this关键字,不加也能正常的跑起来,但是不清晰,加上this关键字,我就是要调用本类中成员变量或方法,而不是本方法的中的一个变量,还有super方法也是一样,是调用父类的的成员变量或者方法,那就加上这个关键字,不要省略,这要靠约束,还有就是程序员的自觉性,他要是死不悔改,那咱也没招。

      注意 上面每个方法都一个this.sequence.clear(),这个估计你一看就明白,但是作为一个系统分析师或是技术经理一定要告诉告诉项目成员,ArrayList和HashMap如果定义成类的成员变量,那你在方法中调用一定要做一个clear的动作,防止数据混乱。如果你发生过一次类似问题的话,比如ArrayList中出现一个“出乎意料”的数据,而你又花费了几个通宵才解决这个问题,那你会有很深刻的印象。

      有了这样一个导演类后,我们的场景类就更容易处理了,牛叉公司要A类型的奔驰车1W辆,B类型的奔驰车100W辆,C类型的宝马车1000W辆,D类型的不需要,非常容易处理,如代码清单11-11所示。

代码清单11-11 导演类

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
public class Client {
 
public static void main(String[] args) {
 
Director director =newDirector();
 
//1W辆A类型的奔驰车
 
for(inti=0;i<10000;i++){
 
director.getABenzModel().run();
 
}
 
//100W辆B类型的奔驰车
 
for(inti=0;i<1000000;i++){
 
director.getBBenzModel().run();
 
}
 
//1000W辆C类型的宝马车
 
for(inti=0;i<10000000;i++){
 
director.getCBMWModel().run();
 
}
 
}
 
}

      清晰,简单吧,我们写程序重构的最终目的就是:简单、清晰,代码是让人看的,不是写完就完事了,我一直在教育我带的团队,Java程序不是像我们前辈写二进制代码、汇编一样,写完基本上就自己能看懂,别人看就跟看天书一样,现在的高级语言,要像写中文汉字一样,你写的,别人能看懂。——这就是建造者模式。

11.2 建造者模式的定义

      建造者模式(Builder Pattern)也叫做生成器模式,其定义如下:

      Separate the construction of a complex object from its representation so that the same construction process can create different representations.将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

      建造者模式的通用类图如图11-4所示。

clip_image008

图11-4 建造者模式通用类图

      在建造者模式中,有如下四个角色:

  • Product 产品类

      通常是实现了模板方法模式,也就是有模板方法和基本方法,这个参考上一章节的模板方法模式。在例子中,BenzModel和BMWModel就属于产品类。

  • Builder 抽象建造者

      规范产品的组建,一般是由子类实现。在例子中,CarBuilder属于抽象建造者。

  • ConcreteBuilder 具体建造者

      实现抽象类定义的所有方法,并且返回一个组件好的对象。在例子中,BenzBuilder和BMWBuilder就属于具体建造者。

  • Director 导演

      负责安排已有模块的顺序,然后告诉Builder开始建造,在上面的例子中就是我们的老大,牛叉公司找到老大,说我要这个,这个,那个类型的车辆模型,然后老大就把命令传递给我,我和我的团队就开始拼命的建造,于是一个项目建设完毕了。

      建造者模式的通用源代码也比较简单,先看Product类,通常它是一个组合或继承(如模板方法模式)产生的类,如代码清单11-12所示。

代码清单11-12 产品类

1
2
3
4
5
6
7
8
9
public class Product {
 
public void doSomething(){
 
//独立业务处理
 
}
 
}

      抽象建造者如代码清单11-13所示。

代码清单11-13 抽象建造者

1
2
3
4
5
6
7
8
9
10
11
public abstract class Builder {
 
//设置产品的不同部分,以获得不同的产品
 
public abstract void setPart();
 
//建造产品
 
public abstract Product buildProduct();
 
}

      其中,setPart方法是零件的配置,什么是零件?其他的对象,获得一个不同零件,或者不同的装配顺序就可能产生不同的产品。具体的建造者如代码清单11-14所示。

代码清单11-14 具体建造者

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
public class ConcreteProduct extends Builder {
 
privateProduct product =newProduct();
 
//设置产品零件
 
public void setPart(){
 
/*
 
* 产品类内的逻辑处理
 
*/
 
}
 
//组建一个产品
 
publicProduct buildProduct() {
 
returnproduct;
 
}
 
}

      需要注意的是,如果有多个产品类就有几个具体的建造者,而且这多个产品类具有相同接口或抽象类,参考我们上面的例子。

导演类如代码清单11-15所示。

代码清单11-15 导演类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Director {
 
privateBuilder builder =newConcreteProduct();
 
//构建不同的产品
 
public Product getAProduct(){
 
builder.setPart();
 
/*
 
* 设置不同的零件,产生不同的产品
 
*/
 
returnbuilder.buildProduct();
 
}
 
}

      导演类就是起到封装的作用,避免高层模块深入到建造者内部的实现类。当然,在建造者模式比较庞大时,导演类可以有多个。

11.3 建造者模式的应用

      1. 建造者模式的优点

  • 封装性

      使用建造者模式可以使客户端不必知道产品内部组成的细节,如例子中我们就不需要关心每一个具体的模型内部是如何实现的,产生的对象类型就是CarModel。

  • 建造者独立,容易扩展

      BenzBuilder和BMWBuilder是相互独立的,对系统的扩展非常有利。

  • 便于控制细节风险

      由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。

      2. 建造者模式的使用场景

  • 相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式。
  • 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可以使用该模式。
  • 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式是非常合适。
  • 在对象创建过程中会使用到系统中的一些其它对象,这些对象在产品对象的创建过程中不易得到时,也可以采用建造者模式封装该对象的创建过程。该种场景,只能是一个补偿方法,因为一个对象不容易获得,而在设计阶段竟然没有发觉,而要通过创建者模式柔化创建过程,本身已经违反设计最初目标。

      3. 建造者模式的注意事项

      建造者模式关注的是的零件类型和装配工艺(顺序),这是它与工厂方法模式最大不同的地方,虽然同为创建类模式,但是注重点不同。

11.4 建造者模式的扩展

      已经不用扩展了,因为我们在汽车模型制造的例子中已经对建造者模式进行了扩展,引入了模板方法模式,可能大家会比较疑惑,为什么在其他介绍设计模式的书籍上创建者模式并不是这样说的,读者请注意,建造者模式中还有一个角色没有说明,就是零件,建造者怎么去建造一个对象?是零件的组装,组装顺序不同对象效能也不同,这才是建造者模式要表达的核心意义,而怎么才能更好的达到这种效果呢?引入模板方法模式是一个非常简单而有效的办法。

      大家看到这里估计就开始犯嘀咕了,这个建造者模式和工厂模式非常相似呀,Yes,是的,是非常相似,但是记住一点你就可以游刃有余的使用了:建造者模式最主要功能是基本方法的调用顺序安排,也就是这些基本方法已经实现了,通俗的说就是零件的装配,顺序不同产生的对象也不同;而工厂方法则重点是创建,创建零件时它的主要职责,你要什么对象我创造一个对象出来,组装顺序则不是他关心的。

11.5 最佳实践

      再次说明,在使用建造者模式的时候考虑一下模板方法模式,别孤立的思考一个模式,僵化的套用一个模式会让受害无穷!

如果你已经看懂本章节举的例子,并认可这种建造者模式,那你就放心使用,比单独使用某些书上的纯建造者是高效、简洁得多。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------原文链接:http://www.cnblogs.com/devinzhang/archive/2012/01/06/2314670.html

1.概念

    将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。 [构建与表示分离,同构建不同表示]

    与抽象工厂的区别:在建造者模式里,有个指导者,由指导者来管理建造者,用户是与指导者联系的,指导者联系建造者最后得到产品。即建造模式可以强制实行一种分步骤进行的建造过程。

  建造模式是将复杂的内部创建封装在内部,对于外部调用的人来说,只需要传入建造者和建造工具指导者和建造者,对于内部是如何建造成成品的,调用者无需关心。

  举个简单的例子,如汽车,有很多部件,车轮,方向盘,发动机还有各种小零件等等,部件很多,但远不止这些,如何将这些部件装配成一部汽车,这个装配过程也很复杂(需要很好的组装技术), builder模式就是为了将部件和组装分开。

2.UML图

3.代码

复制代码
    public interface Builder {     void buildPartA();     void buildPartB();     void buildPartC();       Product getResult();   }    //具体建造工具具体建造者  public class ConcreteBuilder implements Builder {     Part partA, partB, partC;     public void buildPartA() {      //这里是具体如何构建partA的代码    };     public void buildPartB() {       //这里是具体如何构建partB的代码    };      public void buildPartC() {       //这里是具体如何构建partB的代码    };      public Product getResult() {       //返回最后组装成品结果    };   }   //建造者指导者  public class Director {    private Builder builder;       public Director( Builder builder ) {       this.builder = builder;     }     public void construct() {       builder.buildPartA();      builder.buildPartB();      builder.buildPartC();     }   }   public interface Product { }  public interface Part { }  
复制代码
    下面是调用builder的方法:  ConcreteBuilder builder = new ConcreteBuilder();  Director director = new Director( builder );     director.construct();   Product product = builder.getResult();

4.应用场景

在Java的应用中JavaMail使用到了该模式。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

原文链接:http://www.cnitblog.com/sugar/archive/2006/02/22/6808.html

概述

在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法确相对稳定。如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的 “稳定构建算法”不随着需求改变而改变?这就是要说的建造者模式。

本文通过现实生活中的买KFC的例子,用图解的方式来诠释建造者模式。

意图

将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

模型图

生活中的例子

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

实现过程图解

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

客户端:顾客。想去买一套套餐(这里面包括汉堡,可乐,薯条),可以有1号和2号两种套餐供顾客选择。

指导者角色:收银员。知道顾客想要买什么样的套餐,并告诉餐馆员工去准备套餐。

建造者角色:餐馆员工。按照收银员的要求去准备具体的套餐,分别放入汉堡,可乐,薯条等。

产品角色:最后的套餐,所有的东西放在同一个盘子里面。

下面开始我们的买套餐过程。

1.客户创建Derector对象,并用它所想要的Builder对象进行配置。顾客进入KFC店要买套餐,先找到一个收银员,相当于创建了一个指导者对象。这位收银员给出两种套餐供顾客选择:1普通套餐,2黄金套餐。完成的工作如时序图中红色部分所示。

程序实现:

 1using System;
 2using System.Configuration;
 3using System.Reflection;
 4
 5namespace KFC
 6{
 7    /// <summary>
 8    /// Client 类
 9    /// </summary>

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

产品(套餐)类:

 1using System;
 2using System.Collections;
 3
 4namespace KFC
 5{
 6    /// <summary>
 7    /// Food类,即产品类
 8    /// </summary>

 9    public class Food
10    {
11        Hashtable food = new Hashtable();
12        
13        /// <summary>
14        /// 添加食品
15        /// </summary>
16        /// <param name="strName">食品名称</param>
17        /// <param name="Price">价格</param>

18        public void Add(string strName,string Price)
19        {
20            food.Add(strName,Price);
21        }

22        
23        /// <summary>
24        /// 显示食品清单
25        /// </summary>

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.指导者通知建造器。收银员(指导者)告知餐馆员工准备套餐。这里我们准备套餐的顺序是:放入汉堡,可乐倒入杯中,薯条放入盒中,并把这些东西都放在盘子上。这个过程对于普通套餐和黄金套餐来说都是一样的,不同的是它们的汉堡,可乐,薯条价格不同而已。如时序图红色部分所示:

程序实现:

 1using System;
 2
 3namespace KFC
 4{
 5    /// <summary>
 6    /// FoodManager类,即指导者
 7    /// </summary>

 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.建造者处理指导者的要求,并将部件添加到产品中。餐馆员工(建造者)按照收银员要求的把对应的汉堡,可乐,薯条放入盘子中。这部分是建造者模式里面富于变化的部分,因为顾客选择的套餐不同,套餐的组装过程也不同,这步完成产品对象的创建工作。

程序实现:

 1using System;
 2
 3namespace KFC
 4{
 5    /// <summary>
 6    /// Builder类,即抽象建造者类,构造套餐
 7    /// </summary>

 8    public abstract class Builder
 9    {    
10        /// <summary>
11        /// 添加汉堡
12        /// </summary>

13        public abstract void BuildHamb();
14        
15        /// <summary>
16        /// 添加可乐
17        /// </summary>

18        public abstract void BuildCoke();
19        
20        /// <summary>
21        /// 添加薯条
22        /// </summary>

23        public abstract void BuildChip();
24        
25        /// <summary>
26        /// 返回结果
27        /// </summary>
28        /// <returns>食品对象</returns>

29        public abstract Food GetFood();
30    }

31}

32

 1using System;
 2
 3namespace KFC
 4{
 5    /// <summary>
 6    /// NormalBuilder类,具体构造者,普通套餐
 7    /// </summary>

 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

 1using System;
 2
 3namespace KFC
 4{
 5    /// <summary>
 6    /// GoldBuilder类,具体构造者,黄金套餐
 7    /// </summary>

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

完整的客户程序:

 1using System;
 2using System.Configuration;
 3using System.Reflection;
 4
 5namespace KFC
 6{
 7    /// <summary>
 8    /// Client 类
 9    /// </summary>

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//关于建造房屋的例子
  2using System;
  3using System.Collections;
  4
  5/// <summary>
  6/// 抽象建造者
  7/// </summary>

  8public interface IHouse
  9{
 10    bool GetBackyard();
 11    long NoOfRooms();
 12    string  Description();
 13}

 14
 15/// <summary>
 16/// 具体建造者
 17/// </summary>

 18public 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/// <summary>
 64/// 具体建造者
 65/// </summary>

 66public 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
121public interface IRoom
122{
123    string RoomName{get;set;}
124}

125
126public 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/// <summary>
143/// 指导者
144/// </summary>

145public 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/// <summary>
161/// 客户程序
162/// </summary>

163public 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、   ……

总结

建造者模式的实质是解耦组装过程和创建具体部件,使得我们不用去关心每个部件是如何组装的。


0 0