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();  
}

   这种实现,并没有分离产品的构建和表示(直接背离了生成器模式意图)。也许这种实现方式有某些可取之处(我没发现- -),但是已经不是生成器模式了,不要被其误导。

    

 

posted @ 2018-02-08 12:03  一粒粟  阅读(264)  评论(0)    收藏  举报