这次来学习另一个创建型设计模式:Builder生成器模式。
GOF对Builder模式的定义
(1)意图
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
(2)适用性
1. 当创建复杂对象的算法应该独立于该对象的组成部分以及他们的装配方式;
2. 当构造过程必须允许构造的对象有不同的表示;
(3)结构
(4)参与者
Builder:为创建一个Product对象的各个部件指定抽象接口
ConcreteBuilder:1. 实现Builder的接口以构造和装配该产品的各个部件
2. 定义并明确它所创建的表示
3. 提供一个检索产品的接口
Director:构造一个使用Builder接口的对象
Product:1.表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程
2.包含定义组成部件的类,包括将这些部件装配成最终产品的接口
(5)协作
下面序列图说明了Builder和Director是如何与一个客户协作的
代码示例
(1)示例简述
前段时间看了电影《超级战舰》,还不错,所以在举例子的时候就想到用builder模式构建一艘船。注意是“船”,我没说构建“战舰”,船的范围就大了,比如有:航母、驱逐舰、潜艇,当然也包括补给船等后勤船只。而不同的船构造也不同,航母上首先有小飞机,玩过星际的人都知道,还有许多电子设备和一些防空武器;驱逐舰上有鱼雷、导弹、防空炮等等。可以说船的构造还是相对比较复杂的。
那么今天的目的就是:运用builder模式来造一艘“船”。
回忆下Gof对builder模式的描述:“将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示”。有这么几个关键点:1. 对象比较复杂,内部可能包含多个元素或者多个其他对象的组合。2. 对象的表示可能有多种,并且有需求在多种表示中进行切换。3.使用同样的构造过程来创建可能的不同表示。
这样看来,“造一艘船”这个事件中,复杂对象指的是不同种类“船”,“船”的内部可能包括其他对象:“鱼雷”、“飞机”等;“船”有多种表示;相同的构建过程则是描述“造船”这个事件的抽象过程,隐藏具体的构造细节,这样才能通过相同的构造过程得到不同的“船”的表示。
(2)示例说明
首先需要定义下船只的构造规则,方便示例的说明,如下:
航母(AircraftCarrier) = n * 飞机(Aircraft) + n * 防空炮(FlakCannon) + 船体(Hull) + n * 设备(Equipment);
驱逐舰(Destroyer) = n * 鱼雷(Torpedo) + n * 防空炮(FlakCannon) + n * 导弹(Missile) + 船体(Hull) + n * 设备(Equipment);
补给船(Tender) = 船体(Hull) + n * 设备(Equipment);
为什么都是n呢?当然是因为不止一个了!
其实这点非常重要!!!在前面结构图中关于导向器(Director)的描述是这样的:
for all objects in structure {
2: builder -> buildPart();
3: }
注意这个for,为什么不直接是buildPart()调用,而要循环调用呢?我个人的理解是这样的,每一类对象会有一个具体的生成器(ConcreteBuilder)来构造,新增一类对象时,也需要新增一个具体的生成器(ConcreteBuilder)。那么对于对象的类别应该是根据构造结构来分,而不是根据构造结构的不同数量来分,例如五架飞机的航母和十架飞机的航母构造应该是由同一个具体的航母生成器来完成,而不需要新建另一个具体的生成器。组件数量不同的构造过程应该是在导向器(Director)中根据不同的构造算法来实现的。
为什么会特别关注这个细节,是因为看的一些书和资料有的忽略了这点,代码例子看起来很漂亮,但是细细一想由于数量不同带来的大量新建具体生成器的问题就出来了,我想这显然不是GOF的本意。这里有篇文章写的很好点击。
再来看下结构图:
(3)示例代码
废话了这么多该贴代码了:
先是船的一些组件代码,加了很简单的打印。
/**
* <飞机>
*/
class Aircraft
5: {
int no)
7: {
);
9: }
10: }
/**
* <各种设备>
*/
class Equipment
5: {
int no)
7: {
);
9: }
10: }
/**
* <防空炮>
*/
class FlakCannon
5: {
int no)
7: {
);
9: }
10: }
/**
* <船体>
*/
class Hull
5: {
public Hull()
7: {
);
9: }
10: }
/**
* <导弹>
*/
class Missile
5: {
int no)
7: {
);
9: }
10: }
/**
* <鱼雷>
*/
class Torpedo
5: {
int no)
7: {
);
9: }
10: }
再来看下船的代码,具体表示为:航母、驱逐舰、补给船:
/**
* <航母对象>
*/
class AircraftCarrier
5: {
//航母名称
private String name;
8:
//舰载飞机
private List<Aircraft> aircrafts;
11:
//防空炮
private List<FlakCannon> flakCannons;
14:
//船体
private Hull hull;
17:
//设备
private List<Equipment> equipments;
20:
public AircraftCarrier(String name)
22: {
this.name = name;
new ArrayList<Equipment>();
new ArrayList<Aircraft>();
new ArrayList<FlakCannon>();
27: }
//get/set方法忽略
29: }
/**
* <驱逐舰对象>
*/
class Destroyer
5: {
//驱逐舰编号
private String name;
8:
//防空炮
private List<FlakCannon> flakCannons;
11:
//船体
private Hull hull;
14:
//设备
private List<Equipment> equipments;
17:
//鱼雷
private List<Torpedo> torpedos;
20:
//导弹
private List<Missile> missiles;
23:
public Destroyer(String name)
25: {
this.name = name;
new ArrayList<Equipment>();
new ArrayList<FlakCannon>();
new ArrayList<Torpedo>();
new ArrayList<Missile>();
31: }
//get/set方法省略
33: }
/**
* <补给船>
*/
class Tender
5: {
//补给船名称
private String name;
8:
//船体
private Hull hull;
11:
//设备
private List<Equipment> equipments;
14:
public Tender(String name)
16: {
this.name = name;
new ArrayList<Equipment>();
19: }
20:
//get/set方法省略
22: }
下面是生成器(Builder)的代码:
/**
* <舰船Builder>
*/
class ShipBuilder
5: {
//建造船
void buildShip(String name);
8:
//建造船体
void buildHull();
11:
//建造设备
int no);
14:
//建造导弹
int no);
17:
//建造鱼雷
int no);
20:
//建造飞机
int no);
23:
//建造防空炮
int no);
26:
//返回结果
abstract Object getResult();
29: }
生成器可以是抽象类也可以是接口,再来看下几个具体生成器的代码(ConcreteBuilder):
/**
* <建造航母类>
*/
extends ShipBuilder
5: {
private AircraftCarrier aircraftCarrier;
7:
void buildShip(String name)
9: {
new AircraftCarrier(name);
11: }
12:
public AircraftCarrier getResult()
14: {
);
return aircraftCarrier;
17: }
18:
19: @Override
int no)
21: {
new Aircraft(no));
23: }
24:
25: @Override
int no)
27: {
new Equipment(no));
29: }
30:
31: @Override
int no)
33: {
new FlakCannon(no));
35: }
36:
37: @Override
void buildHull()
39: {
new Hull());
41: }
42:
43: @Override
int no)
45: {
46:
47: }
48:
49: @Override
int no)
51: {
52:
53: }
54: }
/**
* <驱逐舰建造>
*/
extends ShipBuilder
5: {
private Destroyer destroyer;
7:
void buildShip(String name)
9: {
new Destroyer(name);
11: }
12:
public Destroyer getResult()
14: {
);
return destroyer;
17: }
18:
19: @Override
int no)
21: {
22:
23: }
24:
25: @Override
int no)
27: {
new Equipment(no));
29: }
30:
31: @Override
int no)
33: {
new FlakCannon(no));
35: }
36:
37: @Override
void buildHull()
39: {
new Hull());
41: }
42:
43: @Override
int no)
45: {
new Missile(no));
47: }
48:
49: @Override
int no)
51: {
new Torpedo(no));
53: }
54: }
/**
* <补给船建造类>
*/
extends ShipBuilder
5: {
private Tender tender;
7:
void buildShip(String name)
9: {
new Tender(name);
11: }
12:
public Tender getResult()
14: {
);
return tender;
17: }
18:
19: @Override
int no)
21: {
22:
23: }
24:
25: @Override
int no)
27: {
new Equipment(no));
29: }
30:
31: @Override
int no)
33: {
34:
35: }
36:
37: @Override
void buildHull()
39: {
new Hull());
41: }
42:
43: @Override
int no)
45: {
46:
47: }
48:
49: @Override
int no)
51: {
52:
53: }
54: }
大家也许注意到了,3个具体生成器中存在空方法,比如在航母生成器中的buildMissile和buildTorpedo,因为我们定义的在航母上没有导弹和鱼雷。
GOF在设计模式中举的例子是用C写的,生成器父类里使用了虚函数,有点像java的abstract,在具体生成器子类中可以选择性的重写父类方法,但是java中没有虚函数,所以只能采用接口或者抽象类,那么在子类中必须实现所有的方法,所以我只能给空的实现了。
再来看一下导向器的代码,在导向器中要对Builder进行配置,并如前面结构图中所画的,在导向器中定义了各种构造的算法:
/**
* Director
*/
class ShipFactory
6: {
private ShipBuilder shipBuilder;
8:
public ShipFactory(ShipBuilder shipBuilder)
10: {
this.shipBuilder = shipBuilder;
12: }
13:
int shipType, String name)
15: {
16: shipBuilder.buildShip(name);
17: shipBuilder.buildHull();
switch (shipType)
19: {
//中国航母
case 1:
22: buildAircraftCarrierCHN();
break;
//外国航母
case 2:
26: buildAircraftCarrierUSA();
break;
//驱逐舰
case 3:
30: buildDrstroyer();
break;
//补给船
case 4:
34: buildTender();
break;
36: }
37: }
38:
void buildAircraftCarrierCHN()
40: {
int i=1; i<6; i++)
42: {
43: shipBuilder.buildEquipment(i);
44: }
int i=1; i<11; i++)
46: {
47: shipBuilder.buildAircraft(i);
48: }
int i=1; i<11; i++)
50: {
51: shipBuilder.buildFlakCannon(i);
52: }
53: }
54:
void buildAircraftCarrierUSA()
56: {
int i=1; i<7; i++)
58: {
59: shipBuilder.buildEquipment(i);
60: }
int i=1; i<12; i++)
62: {
63: shipBuilder.buildAircraft(i);
64: }
int i=1; i<12; i++)
66: {
67: shipBuilder.buildFlakCannon(i);
68: }
69: }
70:
void buildTender()
72: {
int i=1; i<6; i++)
74: {
75: shipBuilder.buildEquipment(i);
76: }
77: }
78:
void buildDrstroyer()
80: {
int i=1; i<6; i++)
82: {
83: shipBuilder.buildEquipment(i);
84: }
int i=1; i<12; i++)
86: {
87: shipBuilder.buildMissile(i);
88: }
int i=1; i<12; i++)
90: {
91: shipBuilder.buildFlakCannon(i);
92: }
int i=1; i<12; i++)
94: {
95: shipBuilder.buildTorpedo(i);
96: }
97: }
98: }
最后看下使用的代码:
class Client
2: {
void main(String[] args)
4: {
new AircraftCarrierBuilder();
// ShipBuilder builder = new DestoryerBuilder();
// ShipBuilder builder = new TenderBuilder();
new ShipFactory(builder);
);
13: builder.getResult();
14: }
15: }
运行结果:
1: 制造了主船体
2: 制造了编号为1的设备
3: 制造了编号为2的设备
4: 制造了编号为3的设备
5: 制造了编号为4的设备
6: 制造了编号为5的设备
7: 生产了编号为1的战斗机
8: 生产了编号为2的战斗机
9: 生产了编号为3的战斗机
10: 生产了编号为4的战斗机
11: 生产了编号为5的战斗机
12: 生产了编号为6的战斗机
13: 生产了编号为7的战斗机
14: 生产了编号为8的战斗机
15: 生产了编号为9的战斗机
16: 生产了编号为10的战斗机
17: 制造了编号为1的防空炮
18: 制造了编号为2的防空炮
19: 制造了编号为3的防空炮
20: 制造了编号为4的防空炮
21: 制造了编号为5的防空炮
22: 制造了编号为6的防空炮
23: 制造了编号为7的防空炮
24: 制造了编号为8的防空炮
25: 制造了编号为9的防空炮
26: 制造了编号为10的防空炮
号航母制造完成
至此代码示例就结束了。
一些思考
(1)产品(Product)需不需要抽象类?
GOF给出了答案:通常情况下,由具体生成器生成的产品,它们的表示相差是如此之大以至于给不同的产品以公共父类没有太大意思。
上面船的例子可能不太贴切,毕竟船之间会有公共的东西,但是换个其他的例子比如构造一副画,不同的画之间很难有共同之处,所以给它们公共接口是没有意义的,它们也不需要这样的接口。因为客户通常用合适的具体生成器来配置导向器,客户的位置使得它很容易的就知道Builder的哪个具体子类被使用和能相应的处理它的产品
(2)多重Builder?
在写上面的例子的时候我是颇有些疑问的,船是由船体、设备、武器等构造出来的,而船体、设备、武器等也是由其他级别更低的组件构造的,它们之间也存在着复杂的构造关系。这中间可能有好几层,知道追溯到螺丝、螺母这样的原子级零件,每一层复杂的构造关系都可以用Builder模式表示。而往上,多艘船可以构造成一个舰队;多个舰队构造成海军等等。
于是我陷入了疑惑,这样多层的Builder模式我该如何表示?直到我看到了一句话:Composite模式通常是由Builder生成的。原来这样多重的Builder关系已经属于另外一种模式了,那么就论Builder模式的话,我们只需要关注其中的一层构造过程就可以了。
关于Builder模式就这么多了,虽然这个模式好像并不经常用。自己理解了Builder模式有一段时间,自觉理解的并不透彻,有些地方还是生搬硬套,也没有实际运用的场景和经验,有一些思考,有一些疑问,希望大家补全,互相学习。