生成器模式——创建型模式(3)
前言
在前两讲,我们介绍了工厂方法和抽象工厂模式,这两种模式都是完成对一个或者若干个内部结构相对简单的对象的创建工作。换句话来说,这样的对象内部之间没有明显的子部分或者说是各个子部分间的“组装”过程。然而在现实世界里,确实存在着这样的对象模型,可以将内部抽象成若干个子部分,而且需要通过一定的组建算法将它们构建在一起形成完整的最终对象。面对类似对象的创建工作,显然工厂模式已经不善长,需要追寻新的模式来更好地应对上述需求。
动机
在实际软件系统中,经常面临着“一个复杂对象”的创建问题,而其通常是由多个子部分通过一定的构建算法组装形成。由于用户需求的变化,这个“复杂对象”的各个子部分也经常面临着剧烈的变化,但是子部分之间组装算法却比较稳定。如何提供一种“封装机制”来隔离出“复杂对象中各个子部分”的变化,从而保持系统对这个复杂对象的“稳定构建算法”不会随着需求的改变而改变?面对这样的需求,生成器模式可以较好地处理。
意图
将一个复杂对象的构建和它的表示分离,便得同样的构建过程可以创建不同的表示。
结构图
- 抽象生成器(Builder)角色:为创建一个Product对象的各个部件指定的抽象接口。
- 具体生成器(ConcreteBuilder)角色:实现Builder的接口以构造和装配该产品的各个部件,同时提供返还组装完毕的产品接口。
- 指导者(Director)角色:构造一个使用Builder接口的对象,调用具体的生成器来创建产品。其本身并没有产品的具体知道,拥有产品具体知识的是具体生成器对象,它们之间才真正具有直接的依赖关系。
- 产品(Product)角色:产品便是具体生成器来要生成的复杂对象。
代码示例
1: public abstract class Builder{
2: public abstract void buildPartA();
3: public abstract void buildPartB();
4: public abstract Product getProduct();
5: }
6:
7: public class ConcreteBuilder1 extends Builder{
8: private Product product;
9: public void buildPartA(){
10: product.addPart("PartA");
11: }
12: public void buildPartB(){
13: product.addPart("PartB");
14: }
15:
16: public Product getProduct(){
17: if(product!=null)
18: return product;
19: else
20: return null;
21: }
22: }
23:
24: public class ConcreteBuilder2 extends Builder{
25: private Product product;
26: public void buildPartA(){
27: product.addPart("PartX");
28: }
29: public void buildPartB(){
30: product.addPart("PartY");
31: }
32:
33: public Product getProduct(){
34: if(product!=null)
35: return product;
36: else
37: return null;
38: }
39: }
40:
41: public class Director{
42: public void construct(Builder builder){
43: builder.buildPartA();
44: builder.buildPartB();
45: }
46: }
47:
48: public class Product{
49: private ArrayList<String> product;
50: public void addPart(String partName){
51: product.add(partName);
52: }
53:
54: public void showProduct(){
55: for (String part : product) {
56: System.out.println(part);
57: }
58: }
59:
60: }
61:
62: public class Client{
63: public static void main(String[] args){
64: Director director=new Director();
65:
66: Builder concreteBuilder1=new ConcreteBuilder1();
67: director.construct(concreteBuilder1);
68: Product product1=concreteBuilder1.getProduct();
69: product1.showProduct();
70:
71: Builder concreteBuilder2=new ConcreteBuilder2();
72: director.construct(concreteBuilder2);
73: Product product2=concreteBuilder2.getProduct();
74: product2.showProduct();
75: }
76: }
从上述示例代码中,我们可以清楚地看到,生成器模式是如何将一个“复杂对象”分步地构建并组装其各个子部分的,虽然在这里我们只是通过字符串来简单地表示各个子部分对象,但是这并不妨碍诠释对生成器模式的demo演示。在实际的软件系统中,各个子部分对象完全有可能通过相应的工厂方法来生成,然后再交由生成器按照特定的构建算法将其组装成一个完整的“复杂对象”。所以,在这里我们不必拘泥于细节的实现,只要理解生成器模式本质和实现方式即可,再通过联想实际生活中的种种模式,相信具有OO思想的你,不难将其抽象成生成器模式所适宜的应用场景。
现实场景
其实,在现实场景中,有很多适用于生成器模式来解决的应用。比如Terrylee所描述的KFC场景,这种模式用于快餐店制作儿童餐。典型的儿童餐包括一个主食,一个辅食,一杯饮料和一个玩具(例如汉堡、炸鸡、可乐和玩具车)。这些在不同的儿童餐中可以是不同的,但是组合成儿童餐的过程是相同的(这是关键点)。无论顾客点的是汉堡,三名治还是鸡肉,过程都是一样的。柜台的员工直接把主食,辅食和玩具放在一起。这些是放在一个袋子中的。饮料被倒入杯中,放在袋子外边。这些过程在相互竞争的餐馆中是同样的。在这样一种场景中,主食、辅食、饮料和玩具根据所选择的套餐种类的不同而不同,但是不管何种套餐,都具有这几大部分,也就是说组装的过程是一致的,不同的只是子部分的实现不同而已,但是这并不妨碍子部分的组装流程。这不完全符合生成器模式的适用场景吗?
这样的例子有很多,比如我们日常工作学习所离不开的电脑,其制造过程何尝不是一种抽象意义上的生成器模式呢?虽然不同品牌的电脑内部的各个组成部件会有所不同,根据自身的定位和需求使用不同厂家生产的部件,但是不管是苹果电脑还是索尼电脑,它们之间的组成元素都是相同的,都有处理器、主板、显卡、显示器等核心组成部件。所谓的电脑生产过程,简单来说就是按照一定顺序将上述所有部件组装起来,而这个组装算法相对来说是稳定不变的,变得只是电脑各个子部分不同而已,与KFC例子类似,完完全全与生成器模式的应用相符合。大家可以充分发挥想像力,联想到现实世界的种种应用场景与生成器模式结合起来。
实现要点
- 生成器模式主要用于“分步骤地构建一个复杂对象”,所谓的“分步骤”其实就是一个稳定的算法,不会因为创建不同的复杂对象而发生改变,变的部分是复杂对象中各个子部件实现。另外,Builder类接口必须有足够普遍,以便为各种类型的具体生成器构造产品。
- 生成器模式中所创建的产品对象不需要抽象类,因为由具体生成器生成的产品,它们的表示相差如此之大以至于给不同的产品以公共父类没有太大必要,也不太可能提取出一个公共父类。
- 在生成器用于构建各种子部件的接口方法,完全可以不是抽象方法(虽然我们示例代码是这样),提供一个默认实现,具体生成器只需要重写需要重写的接口即可,对于一些相对“通用”的接口,在情况允许的条件下,可以选择直接使用默认实现。
运用效果
- 松散耦合:生成器模式可以用同一个构建算法构建出表现上完全不同的产品,实现产品构建和产品表现的分离。正是将产品的构建过程独立开来,才使得生成器模式与具体产品的表现松散耦合,从而使得构建算法可以复用,而具体产品表现也可以灵活地、方便地扩展和切换。
- 可以很容易地改变产品的内部表示:在生成器模式中,由于指导者只是通过Builder来构建具体产品,但是具体产品中的各个子部件的创建和装配方式却被builder接口隐藏起来,Director并不清楚这些具体实现细节。也正是因为这样,需要改变产品的内部表象时,我们只需要切换不同的Builder实现对象即可,而Director角色无需作出任何的改变。
- 复用性更好:生成器模式分离了构建算法和具体产品实现,这样使得“稳定”的构建算法可以达到复用的效果,同样的效果,具体产品的子部件的实现亦可以复用,一个相同的子部件实现可以配合不同的构建算法来使用。
适用性
- 需要创建的产品对象有复杂的内部结构时(这是基础,不然不需要使用分步构建算法来创建和组装产品的各个子部件部分)。
- 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
- 需要创建的复杂对象内部的各个子部分有一定的关联性或者依赖性,这样在可以通过生成器来强迫生成一定的创建和组装顺序。
- 当构造过程必须允许被构造的对象有不同的表示时。因为如果通过相同的构造过程只有一种内部表象时,就无所谓利用生成器模式呢,这时或许使用工厂方式模式更合适、方便些。
相关模式
- 生成器模式与工厂方法模式:在具体的生成器实现中,需要选择具体的部件实现。一个可靠的方案就是将部件的实现工厂化,通过工厂方法来获取具体子部件实现对象,然后再进行子部件的装配。
- 生成器模式和抽象工厂模式:在生成器模式的Builder实现中,需要创建各个部件对象,而这些部件是相互有关联的,通常是构成一个复杂对象的部件对象。也就是具体生成器需要获取构成一个复杂对象的产品族,而产品族的实现可以使用抽象工厂来实现,这样复杂对象的子部件由抽象工厂模式来创建,而生成器模式负责对复杂对象各个子部件的组装构建工作。
- 生成器模式与模板方法模式:两者有很大的相似和差异性。相似性:都是定义一个固定的算法骨架,然后把算法中的某些步骤交给其他类来完成,都能实现整体算法和某些具体步骤的分离。差异性:首先是目的性,生成器模式是用来构建复杂对象的,而模式方法是用来定义算法骨架的,尤其是一些复杂的业务功能的处理算法骨架;其次是模式的实现上,生成器使用委托的方式(注意这里的委托指的是Director委托具体的Builder来创建具体产品,而不是指具体生成器的实现方式),而模板方法是使用继承的方式(这里是主要是从两种模式的使用角度上来说的,其实个人认为你也可以生成器模式也是通过继承的方式来完成的)。
- 生成器模式与组合模式:建于复杂的组合结构,可以通过生成器模式一步步地进行构建组合结构,通过分步构建的方式来完成对组合结构对象的创建。
总结
生成器模式的本质:分离整体构建算法和部件构造表示。构建一个复杂对象,需要将整体的构建过程与复杂对象子部件的构建过程分离开来,这样才能使得程序结构更松散、易扩展,复用性好,同样也会使代码逻辑更清晰,意图更明显。生成器模式的重心还是在于分离整体构建算法与子部件的构建,分步骤构建对象只不过是整体构建算法的一个简单表现,或者说是一个附带产物。说了这么多,也摘抄了其他优秀博文和书籍的经典论述,希望大家对生成器模式会有一个全面、深刻的理解和掌握。下一篇将介绍原型模式,敬请期待。
参考资料:
- 程杰著《大话设计模式》一书
- 陈臣等著《研磨设计模式》一书
- GOF著《设计模式》一书
- Terrylee .Net设计模式系列文章
- 吕震宇老师 设计模式系列文章