Builder生成器/建造者模式(java)
引
最近在学习《领域驱动设计:软件核心复杂性应对之道》,在第9章节讲解Specification模式时,对于Specification的场景
- 验证
- 查询
- 构建
第三种方式(构建)时,作者提到了生成器模式,因为这个模式自己在项目中从来没有使用过,因此再次做个回顾和复习。
模式定义
意图:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
生成器模式属于创建型模式。
注意:个人建议,不要完全相信网上对于模式的理解,千人千语,我曾看到过有人在博客中将生成器模式说成是结构型模式-----太过分的误人子弟。生成器模式/建造者模式从名字上就很容易了解属于哪一类(同时理解创建型模式有个很好记忆的方法-------它最终是为了干什么-----组装/创建出对象,例如:单例-----创建一个对象、工厂------前一篇策略模式中有提过通过隔离/映射方式来创建对象、原型------通过克隆拷贝方式生成对象,生成器---------通过组装生成对象【不管它们的中间过程如何,最终目的就是为了创建出对象!】)。看过网上的模式,最好能够去查阅GOF设计模式原本,做个对比和对照(如果你水平可以,我更建议直接去看GOF,少走很多弯路- - )
阐述和讨论
首先看看Builder(生成器/建造者,以下讨论中直接使用Builder来表示)类图:

如果直接看这个图,会有些懵逼(除非你很熟悉这个模式),发现它和前一篇的Strategy模式类图特别相似。(模式从最抽象的角度说基本都是增加了中间层,有很多类图甚至完全一样,只通过图去理解模式远远不够,需要了解它的目的和内涵-----为了解决什么问题)。
认真看图,是否首先有几个疑问:
1)Director为什么使用的是类而不是接口?
如果有这个问题,请再次认真对一遍Builder模式意图后半句【同样的构建过程可以创建不同的表示】,而构建过程是由Director来控制的,如果使用接口,那么势必要从有不同的实现类,而这些类势必会有不同的构建过程(如果构建过程相同,又为何要从实现接口?如果不同,那么违背了模式的意图,意义何在呢?真出现这种情况,或许再次考虑设计和模式的选用是否合适)。
2)类图中的Builder接口为什么直接依赖于具体的Product类,而不是依赖于产品接口呢,这不是违背了“依赖接口,而不要依赖具体”这个“原则”?
这个问题,GOF中也提到了:

通过这里,也想说:一味信奉原则不如根本就不知道原则【尽信书不如无书】,模式,原则请根据自己的当前情况去思考和决定完全遵守与否。
建造者模式的核心在于:同样的构建过程可以创建不同的表示。这句话一定要理解。在上面类图中,分析ConcreteBuilderA,通过接口内部的方法,
大致可以明白可能要经过builderPart1、builderPart2、builderPart3这三个过程/步骤【为什么说可能,如果看到这里就认为一定是三步,那么可能被坑了- -】。
而具体构造如何构造由Director决定(如果在Director中的builder函数就是依次调用了builderPart1、builderPart2、builderPart3,那么就是刚好三步,
但是这不一定,如果将builderPart1看做造头颅,builderPart2是创建胳膊,builderPart3是创建其他部分,那么就有了 builderPart1 , builderPart2 ,builderPart2 ,
builderPart3-------这里假设builderPart2一次一只创建【opps,原谅我想象力弱小】)。稍微有点懂?看个具体例子(直接拿GOF的):

这里 RTFReader是作为了Director , TextConverter是Builder【最上面类图中的Builder】,这里大致背景是将RTF文档做转换,根据获取的RTF文档中的字符来
分别进行转换。(那么你想想,步骤能是确定的吗?如果整篇RTF文档都是CHAR,是不是只是调用了ConvertCharacter方法?)。同样的构建过程【多分析下while过程】,
但是如果builder是不同的【ASCIIConvert、TexConverter...】得到的表示就是不同的【再次回顾下模式意图:将一个复杂对象(这里自然就是ASCIIText、TexText了)的构建
(在RTFReader---------Director中)和它的表示(ASCIIText和TexText表示能一样吗)分离,同样的构建过程可以创建不同的表示!】。
在这里,TextConvert中并没有直接包含获得产品的方法,而是将获得产品的方法放在了具体的Builder中了,而这个模式中,Client是本身知道要具体创建Builder的,这样,直接
去调用具体Builder的获得产品方法【GetASCIIText/GetTexText】也就无伤大雅了,当然也可以写一个getProduct/getResult方法放入到TextConvertert接口中,利用多态来获取也是可以,
不过名称获取没有getASCIIText清晰(OO中,我更喜欢放到接口中,使用多态。),实现方式没有好坏,自己决定。
最后,网上有人在获得产品的方法中这样写:
1 public Product getResult(){ 2 builderPart1(); 3 builderPart2(); 4 builderPart3(); 5 6 return Product; 7 }
或者提供一个抽象的composite:
public abstract composite(){ builderPart1(); builderPart2(); builderPart3(); } // 在Director中提供getResult public Product getResult(){ return builder.composite(); }
这种实现,并没有分离产品的构建和表示(直接背离了生成器模式意图)。也许这种实现方式有某些可取之处(我没发现- -),但是已经不是生成器模式了,不要被其误导。

浙公网安备 33010602011771号