园子里一个兄弟写的文章,非常不错。原文链接:http://www.cnblogs.com/happyhippy/archive/2010/09/01/1814287.html
Builder模式的误区:将复杂对象的构建进行封装,就是Builder模式了吗?

最近重读GOF的《设计模式》,读到Builder模式的时候,发现还是不能领悟;网上搜了下其他人的解释,发现很多人都用错了Builder模式,结构形似Builder,实际上却更像Template、或者Factory Method,或者四不像,并没有体现出Builder模式的思想和威力;通过对比学习,也逐渐加深了我对Builder模式的认识,于是就有了这篇文章。

0. GOF - Builder模式

下面是GOF对Builder模式的部分阐述,先列出来,用于与后文中的错误案例进行对比。文字很精辟,不易理解;但若真正理解了,会发现这些文字对已经将Builder模式的精髓描述完了。
(1) 意图:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

(2) 适用性:
同时满足以下情况的时候可以使用Builder模式
    a. 当创建复杂对象的算法应该独立于该对象的组成部分以及他们的装配方式
    b. 当构造过程必须允许构造的对象有不同的表示

(3) 结构:

留意图中红色注释部分,尤其是循环,灰常灰常重要,呵呵,这也是很多人在使用Builder模式时所忽略的部分。(想想,为什么不是一句builder->BuildPart()就够了,为什么要有这个循环呢?)

(4) 协作: 

(5) 相关模式:
Composite通常是用Builder生成的

先不解释这些文字,先看两个例子,看看使用Builder模式的误区。这两个例子皆来自书上(对不起,我不是恶意挑刺,实在是两位大哥太出名了-_-,还请见谅)。

1. 组合不同数量的现有部件,需要定义新的Builder子类吗?

先看一个例子:Builder模式应用实践


(此图从原文中copy来的)

这个例子中,设备(Equipment)是一个复杂对象,由一个Machine和一个(或多个)输入端口(InputPort)或者输出端口(OutputPort)组成;此设计中定义了一个LCDFactory(充当导向器[Director]的角色)、一个设备生成器(EQPBuilder),及三个ConcreteBuilder:
InputEQPBuilder生成的Equipment = 1个Machine  + 1个InputPort;
OutputEQPBuilder生成的Equipment = 1个Machine + 1个OutputPort;
IOPutEQPBuilder生成的Equipment = 1个Machine + 1 个InputPort + 1个OutputPort;

此设计对复杂对象Equipment的创建过程进行了封装,在应对需求变化上,作者的解释是:“例如要求创建的Equipment包含一个Machine对象,一个Input类型的Port,两个Output类型的Port,那么我们可以在不修改原有程序集的前提下,新定义一个IO2PutEQPBuilder类,并继承自抽象类EQPBuilder”。

也就是说,每当要给设备增加端口的时候,我们就要创建新的Builder子类。我们把这个需求扩大化,如果要创建一个Equipment,其可能包含0~M(M>=0)个InputPort、0~N(N>=0)个OutputPort,这样可能组合出(M+1) * (N + 1)个Equipment,因此我们就需要创建(M + 1) * (N + 1) 个ConcreteBuilder,这会带来Builder子类数量的急剧膨胀;其本质上是通过继承来达到构建不同的Equipment。这与Builder模式的思想是相违背的,结合Builder模式的结构图来看,导向器(Diretor)是调用BuildPart()方法,来将部件(Part)组合到目标Product中的;如果只是组合不同数量的现有部件,则不用定义新的ConcreteBuilder

因此,虽然这个类图几乎形似Builder模式,但却并不是Builder模式的应用。

2. Builder模式就是创建复杂对象的模板吗?

一些Builder模式的“应用”,感觉更像是一个创建复杂对象的模板;而对Builder模式与Template Method模式的区分,则认为Builder模式是侧重于创建复杂对象,而Template Mehod则侧重于对象的行为。

在我看来,这个观点是错误的。如果把Builder当作一个创建复杂对象的模板,则基本上可以断定,Builder模式被误用了。Builder模式的类图结构中,装配复杂对象的组成部分,是用BuilderPart()方法来定义,如果我们把这个装配操作视为一个操作行为,是不是意味着这种情况下的Builder模式就是一个Template Method了呢?我认为答案是否定的。Builder模式与Template Method模式有着天壤之别,二者毫不相干;前者偏重于通过聚合来组装对象,后者偏重于通过继承来重写对象的行为

下面再来看下,《大话设计模式》中的例子:该应用中,要求画一个小人,要有头、身体 、两手、两脚。给出的类图结构如下所示:
 
(此图从该书中copy来的)

在这个例子中,原作者要求小人不能缺胳膊少腿;我猜测,其中也隐含着小人不能有三头六臂。作者认为“这里构造小人的‘过程’是稳定的,都需要头身手脚,而具体构造的‘细节’是不同的,有胖有瘦有高有矮”。在应对高矮胖瘦的需求变化时,只需要增加新的PersonBuilder子类就行了。如果需要细化‘细节’,“比如人的五官、手的上臂、前臂和手掌,大腿小腿”,“这些细节是每个具体的小人都需要构建的”,则需要将这些接口加到Builder接口中;Builder模式中的Builder接口必须要“足够普遍,以便各种类型的具体建造者构造”。

虽然这个类图几乎神似Builder模式,但细细斟酌,却也有不妥的地方:
(1) 需求变化时,人可高可矮可胖可瘦,所以可以该设计中,就可为高矮胖瘦分别创建ConcreteBuilder,但假如需求继续变化,要实现高胖、矮胖、高瘦、矮瘦呢,是否需要继续扩展Builder?在我的理解中,高矮胖瘦体现的是“人的特征”;游戏中小人的特征可能非常多(方脑壳、圆脑壳、O型腿、八字脚、咸猪手),为创建每个不同特征的小人,都配置一个Builder,可能不太现实。

(2) 此示例中,原作者认为“构造小人的‘过程’是稳定的”;偏重于去说明,面对“细节”(即组成人的部件:头、身、手、脚)的差异,需要创建新的ConcreteBuilder;这就容易给人造成一种错觉:造小人的过程是一个类似于一个模板,构造不同的小人时,需要继承该模板、重写差异来实现新小人的生成。

(3) Builder模式构造出来的不同种类的Product,这些Product的组成部分(part)相互不能进行替换或组合,否者将会带来ConcreteBuilder数量的急剧膨胀。这一点在后面再来说。

Builder模式的核心是“聚合”,这个例子中,并没有把Builder模式的思想体现出来。

3. Builder模式示例

为了避免构造新的示例,便于比较和理解,我直接在上面两个例子的基础上进行修改:

3.1 改进版的EQPBuilder

  
与之前的EQPBuilder的区别在哪儿呢?

(1). InputPort、OutputPort、Machine等,是复杂对象Equipment的组成部分,这些部件的装配方式在AddXXOOPort、BuildMachine等方法中定义;而如何根据这些部件来创建复杂Equiment的算法,在导向器类LCDFactory中定义;这就使得“创建复杂对象的算法独立于该对象的组成部分以及他们的装配方式”。

(2). AddXXOOPort、BuildXXOOMachine等接口,封装了部件(Port、machine)与产品(Equipment)的装配方式,Add操作可能比较复杂,其可能封装了初始化Port设备、执行插拔、焊接等操作;LCDFactory作为创建设备导向器,如果其构造设备的过程中,要增加多个Port,则只需要多次复用Builder的Add操作即可。因此,如果只是组合不同数量的现有部件,本质上只是“创建复杂对象的算法”被改变了;因为我们只用调整算法部分LCDFactory就可以了,而不用去创建新的ConcreteBuilder;

(3). 当且仅当新的部件(SuperXXOO)需要加入到系统时,才需要去创建新的ConcreteBuilder;如果要创建SuperEquipment,我们只需要将SuperEQPBuilder的示例传递给LCDFactory就足够了,这里复用了原有的构造Equipment的算法

3.2 改进版的PersonBuilder

与之前PersonBuilder的差别:

(1). 奥特曼是机器人,变形金刚是汽车人,统统可以抽象出来当人,有头有身体有胳膊有腿(暂时不考虑汽车人变形的情况)。BuildPart(Head/Body/Arm/Leg)封装了创建部件、并装配人身上的操作;这个Build操作供导向器复用。PersonDirector定义了创建人的多种算法,不同算法调用BuildPart()的顺序和次数不同,可以生成出具有1头1身2臂2腿的常规人,也可以创建独臂刀客,还可以创建三头六臂的超人等。

(2). 构造小人的算法是灵活多变的,该算法在PersonDirector中定义;至于如何变,可以用其他设计模式来实现,这里不与讨论,这里侧重的是Builder模式的应用。只要我们将不同的ConcreteBuilder传递给同一PersonDirector,就可以得到不同的人(人类、机器人、汽车人),从而复用了创建Person的算法,达到同样的构件过程可以创建不同的表示。

3.3 特例:StringBuilder

 
    在这个Builder模式的实现中,Client同时充当了Director的角色;StringBuilder同时充当了Builder接口和ConcreteBuilder。这是一个最简化的Builder模式的实现。

   1: //Client同时充当了Director的角色
   2: StringBuilder builder = new StringBuilder();
   3: builder.Append("happyhippy");
   4: builder.Append(".cnblogs");
   5: builder.Append(".com");
   6: //返回string对象:happyhippy.cnblogs.com
   7: builder.ToString(); 

4. Builder模式的核心思想

将一个“复杂对象的构建算法”与它的“部件及组装方式”分离,使得构件算法和组装方式可以独立应对变化;复用同样的构建算法可以创建不同的表示,不同的构建过程可以复用相同的部件组装方式

抽象的Builder类,为导向者可能要求创建的每一个构件(Part)定义一个操作(接口)。这些操作缺省情况下什么都不做。一个ConcreteBuilder类对它所感兴趣的构建重定义这些操作。每个ConcreteBuilder包含了创建和装配一个特定产品的所有代码(注意:ConcreteBuilder只是提供了使用部件装配产品的操作接口,但不提供具体的装配算法,装配算法在导向器[Director]中定义)。这些代码只需要写一次;然后不同的Director可以复用它,以在相同部件集合的基础上构建不同的Product。

回过头再来看,类图结构中对Director的注释,为什么不是一句builder->BuildPart()就够了,为什么要有这个循环呢?BuildPart方法封装了创建Part、并组装到Product中的操作,循环调用调用多次时,可以反复复用BuildPart操作,让目标Product聚合多个Part。再进一步:如果Part中可以聚合多个Part,然后递归下去,可以组合成一颗树型结构,这就是Composite了;在来理解相关模式中的这句话:“Composite通常是用Builder生成的”,就很容易理解了。

另外,需要指出的一点。单纯的Builder模式中,“不同Product类型”的组成部件之间,不能进行组合或替换。譬如上面的两个示例中:组成普通Equipment的普通InputPort、OutputPort、Machine,不允许与组成SuperEquipment的SuperInputPort、SuperOutputPort、SuperMachine进行组合创建新的Equipment;人的头身臂腿,与奥特曼的头身臂腿,或者汽车人的头身臂腿,三者之间的部件不能兼容或替换。这一点GOF在DP中并没有说明,但是在他们给出的两个例子中,充分体现了这一点:RTF的三个转换器,ASCIIConvert只负责组合ASCIICharactar,TeXConverter之负责组合自身格式的部件(Charactor、FontChange、Paragraph),TextWidgetConverter同理;因此不可能出现由TextWidget格式的Charactor和TeX格式的Paragraph组合而成的Text。GOF的另一个Builder模式的应用示例是StandardMazeBuilder与CountingMazeBuilder;GOF在介绍创建型模式时,前后多次用到Wall/BombedWall、Room/RoomWithABomb,为什么这里GOF偏偏不用BombedMazeBuilder,而别出心裁搞出个CountingMazeBuilder;他们很巧妙地回避了部件替换问题。假如允许“不同Product类型”的组成部件之间进行组合或替换,譬如我们允许将奥特曼的头与变形金刚的头进行互换,或者允许将机器人的身体替换的人的身体来构建出钢铁侠,或者使用其他组合来构建金刚狼,我们该怎么办呢?这个问题已经超出了Builder模式的范畴,先留着。

相关模式[DP]:Abstract Factory与Builder相似,因为它可以创建复杂对象。主要的区别是Builder模式着重于一步步构造一个复杂对象,而Abstract Factory着重于多个系列的产品对象(简单的或复杂的)。Builder是最后一步返回产品,而Abstract Factory是立即返回产品。Composite通常是用Builder生成的。

happyhippy作者:Silent Void 
出处:http://happyhippy.cnblogs.com/
转载须保留此声明,并注明在文章起始位置给出原文链接。

转载于:https://www.cnblogs.com/lstj/p/3362884.html

深入理解Builder模式(转载)相关推荐

  1. 设计模式-Builder模式

    目录 一个例子(做汤) 人工做汤 机器做汤(使用Builder模式) 优缺点 优点 缺点 Builder模式属于创建型模式. 它是将一个复杂对象的构建过程隐藏起来,让使用者只关系自己要生成什么样的对象 ...

  2. GOF对Builder模式的定义(转载)

    (1)意图 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. (2)适用性 1. 当创建复杂对象的算法应该独立于该对象的组成部分以及他们的装配方式: 2. 当构造过程必须允许 ...

  3. Android常用设计模式之Builder模式理解

    Android常用设计模式之Builder模式 1 单例模式 2 Builder模式 Builder模式的应用场景 总结 1 单例模式 单例模式之前有详细的介绍,可移步到链接: 常见的单例模式及其特点 ...

  4. Builder模式简单理解

    Builder 模式 Builder模式 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 那么Builder模式的用场景呢? (1) 相同的方法,不同的执行顺序,产生不同的 ...

  5. 深入理解建造者模式 ——组装复杂的实例

    历史文章回顾: 设计模式专栏 深入理解单例模式 深入理解工厂模式 历史优质文章推荐: 分布式系统的经典基础理论 可能是最漂亮的Spring事务管理详解 面试中关于Java虚拟机(jvm)的问题看这篇就 ...

  6. Java设计模式——Builder模式

    前言 之前写Android程序的时候,经常会用到Dialog(对话框)这个控件.我们在使用Dialog,比如AlertDialog的时候就用到了这里要说明的Builder模式.现在我们来看一下这样的一 ...

  7. 使用Builder模式创建复杂可选参数对象

    在新建对象时,若需要对大量可选参数进行赋值,最常见的做法是JavaBeans模式,即调用一个无参构造方法创建对象,然后调用setter方法来设置每个必要的参数,以及每个相关的可选参数.代码示例如下: ...

  8. effective java 3th item2:考虑 builder 模式,当构造器参数过多的时候

    yiaz 读书笔记,翻译于 effective java 3th 英文版,可能有些地方有错误.欢迎指正. 静态工厂方法和构造器都有一个限制:当有许多参数的时候,它们不能很好的扩展. 比如试想下如下场景 ...

  9. 设计模式之构建者(Builder)模式

    在五大设计原则的基础上经过GOF(四人组)的总结,得出了23种经典设计模式,其中分为三大类:创建型(5种).结构型(7种).行为型(11种).今天对创建型中的构建者(Builder)模式的思想进行了一 ...

  10. Intellij idea generate builder 插件-用于自动生成builder模式代码

    2019独角兽企业重金招聘Python工程师标准>>> 目的:开发中喜欢builder模式去构造一个实例,而当一个对象的属性过多的时候,手动的去完成一个类的builder是很繁琐的: ...

最新文章

  1. 【合并单元格】纵向合并单元格之前对数组处理【针对饿了么element的table的span-method合并行或列的计算方法】
  2. 一本通1655数三角形
  3. webview js 与 java 调用参数问题。
  4. 她被“誉为”中科院最美女院士,52岁依然貌美如花?气质不输女星
  5. 好用的书法字体素材,可以用于各种项目;从商标和品牌到邀请、海报等
  6. PyCharm——如果不小心修改了第三方库文件,怎么办?
  7. 乌班图16.04网卡驱动安装
  8. 加密 CryptoJS DES
  9. python的flask前端显示图片_Python flask框架如何显示图像到web页面
  10. 【2014年计划】IT之路
  11. android 11.0 12.0Camera2 去掉后置摄像头 仅支持前置摄像头功能
  12. Shell - cp
  13. 分节符是什么?怎么利用分节符设置某一页文档的页眉页脚?
  14. Arduino C语言 240*240 TFT 显示屏绘制表盘手把手教学,粗暴易懂
  15. STM32物联网套件基础版03-控制继电器
  16. android rom 制作工具,ROM工具箱(ROM Toolbox Pro)
  17. lattice fpga ddr3 读写控制
  18. pygame库-Surface类-blit方法的两个参数(source, dest)的含义
  19. nmn抗衰老有哪些品牌,nmn最新排名情况,掏心窝子推荐
  20. GraalVM - 云原生时代的 Java 笔记

热门文章

  1. 多水下机器人协同定位
  2. Windows 错误代码
  3. linux中shift用法,Linux shell脚本中shift的用法说明
  4. 如何用Hexo搭建个人博客网站
  5. 如何实现2019新年愿望:梦想还是要有的,但不能靠“万一实现了呢
  6. Camunda与springboot集成入门实战
  7. VS2013MFC对话框工程学习笔记二 - 了结布局和一些基本的窗口组件
  8. 概念模型向逻辑模型的转换规则
  9. 计算机pdf转换word,PDF怎么转换成Word?解决PDF转Word的小妙招
  10. GEE实战3:利用GEE获取区域的长系列日均气温变化【逐日气温变化分析】