代码改变世界

再论桥接模式(上)纸上谈兵

2009-12-03 21:46  金色海洋(jyk)  阅读(1846)  评论(36编辑  收藏  举报

 

 
声明:
1、 这里不是讲解桥接模式,因为我觉得我没有那个实力,我现在还没有完全理解桥接模式。
2、 这里只是想把我这几天的思考、在群里的讨论整理一下,给自己的学习道路上留下一个脚印
3、 因为前面写了一篇,现在看来有很多的问题,因为那时候并没有理解“抽象部分”,所以有很多的问题,现在的理解比那时侯又进了一步,所以需要在解释一下。
4、 我最怕的就是误导新人,误人子弟可是很大的罪过,所以我希望大家能够多多讨论,多多批评,哪怕我现在的理解还是错的,那也是新人一个警示,不要犯我这样的错误。


目的:
这一篇想弄明白下面几个问题:
1、 什么是抽象?
2、 抽象部分是什么?
3、 抽象部分是如何与实现部分分离的?
4、 抽象部分是如何独立变化的?(重点在于变化)

 

前言
  我也是一直在不断的理解、消化各种知识,比如面向对象、设计模式等,每次小有收获的时候都想把成果写出来,于是我的博客里就有了一篇篇小小的博文。虽然写出来了,但是并不能保证就一定是正确的,写出来与人分享,与人讨论,检查自己的想法是否正确。只要有自己的想法就行,谁能保证自己的想法永远是正确的呢?在此感谢dudu为我们提供了一个与人交流讨论的平台!

 

  我们都是一步一步走过来的,可能当时觉得自己的想法是正确的,但是现在回过头来一看,当时的想法可能就是错误的。如果错了,那么我们改进原来的想法。于是我们在不断的进步。如果您觉得现在的想法和以前是一样的,那么说明您要么一直停滞不前,要么已经到达巅峰了,呵呵。

 

  前面写的那篇关于页面和桥接的确是写的不好,那时对桥接的理解还比较片面,现在又有了新的理解,所以我觉得有必要在说明一下,以免误导大家。

先出一个桥接模式的UML图。(来自TerryLee的http://www.cnblogs.com/Terrylee/archive/2006/02/24/336652.html

 

 

 

 

案例一:


  在网上找到了一个例子,我觉得很适合初步了解桥接模式。http://blog.csdn.net/abcdwxc/archive/2008/02/03/2079334.aspx 感兴趣的话,可以去看看,这里就不过多的引用哪里的描述了。


  他先说了一个不好的设计,蜡笔,如下图,这个就是一个排列,3种型号的蜡笔×12种颜色=36。这个数量就比较恐怖了,那么如何来减少这个数量呢?毛笔与颜料。一种毛笔可以用十二种颜色。颜色的变化,不用去考虑毛笔;毛笔的变化,也不用去考虑颜色。这个这个就是对桥接模式的第一步的理解吧。当初我的想法也是这样的,但是现在有个疑问。

 

 

 

 

 

  这么设计确实是把毛笔和颜色解耦了,也做到了“二者的独立变化”,但是毛笔和颜色谁是抽象部分,谁是实现部分呢?或者说抽象部分是谁?
原文的解释是“抽象层面的概念是:‘毛笔用颜料作画’”。这个好像没有什么错误,但是没有明确指出哪里是抽象部分,就是说UML图里,哪个地方是抽象部分?这个并没有明确的说明。


如果说毛笔和颜料都是抽象部分,那么毛笔的型号和各种颜色就是实现部分了。这就有两个疑问:


1、 他们解耦了吗?如果这样的话,那不就是说父类和子类是解耦的了。这显然是不对的。
2、 各种颜色独立变化了,毛笔型号也独立变化了,但是这都是实现部分的变化呀,抽象部分变化了吗?

 

看来这种划分抽象部分和实现部分的方法是不对的,应该“竖着”分,即左面的毛笔部分是抽象部分,右面的颜色是实现部分。但是这样也有疑问呀?


1、 左面的毛笔部分怎么就是抽象部分了,抽象在哪里?如果说毛笔是抽象的那还可以理解,但是大号毛笔就是具体的了,是独立的。
2、 毛笔并不依赖于颜色,他和颜色之间的关系并不密切,本来就是分开的,所以不用特意的去分离。
3、 毛笔并不需要用颜色来体现出来,就是说毛笔不需要用颜色来实现。

 

  这么分也是不对的,那么如何划分才是对的呢?其实毛笔和毛笔里的成员——颜色,和在一起才是抽象部分。现在实现了第二个目的,那么这个抽象部分又是如何与实现部分分离的?又是如何独立变化的呢?


  我觉得这个例子并没有体现出来,或者说是我没有理解出来。我们再换一个例子吧。


案例二:

  LoveBaoBao-DP 和我说了一下《设计模式解析2》里的桥接模式里的例子——画图。我没看过这本书,完全是根据他的讲述来理解的,因为经过了一个人的转述,所以我觉得我下面的UML图很可能和《设计模式解析2》里的是不一样的。

 

下面是 LoveBaoBao-DP 的描述:(案例二和案例一完全没有关系,请暂时忘记案例一)

 


画图
  图形 具体可以有,圆形,方形,菱形等,笔具体有,圆珠笔,钢笔,毛笔。那么这里有个问题,画图的时候,用钢笔画圆形,用毛笔画圆形,用毛笔画方形。这里笔和图形的子类具有耦合。简单说,图形是抽象,具体怎么画,是实现。也就是笔。如果,抽象出来图形,然后再给各种笔做个抽象父类,那么 就是两个抽象的依赖,图形的子类不用关心笔的子类了。这样达到了,抽象跟实现分离。

  延缓排列乘积,任意两端加一个子,不用关心因此而出现的那些组合种类,组合的总类是增加了,但是你不用去考虑,达到了延迟,没有桥接,出现一个子,就要写出所有组合的可能。

 

 

 

我按照自己的理解画了一个UML。
【UML】

 

(上面的是原来的UML图,根据的提问又修改了一下,感觉修改之后就更像了,呵呵。下面是修改后的。)

 


 

  在说这个“画图”之前我们先看看桥接模式的相关说明:(引自http://www.cnblogs.com/lds85930/articles/643166.html
 

 

桥梁模式的用意

  【GOF95】在提出桥梁模式的时候指出,桥梁模式的用意是"将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化"。这句话有三个关键词,也就是抽象化、实现化和脱耦。

 

抽象化

  存在于多个实体中的共同的概念性联系,就是抽象化。作为一个过程,抽象化就是忽略一些信息,从而把不同的实体当做同样的实体对待【LISKOV94】。

 

实现化

  抽象化给出的具体实现,就是实现化。

 

脱耦

  所谓耦合,就是两个实体的行为的某种强关联。而将它们的强关联去掉,就是耦合的解脱,或称脱耦。在这里,脱耦是指将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联。

将两个角色之间的继承关系改为聚合关系,就是将它们之间的强关联改换成为弱关联。因此,桥梁模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以相对独立地变化。这就是桥梁模式的用意。

 

含有两个等级结构,也就是:

  • 由抽象化角色和修正抽象化角色组成的抽象化等级结构。
  • 由实现化角色和两个具体实现化角色所组成的实现化等级结构。

桥梁模式所涉及的角色有:

  • 抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用。
  • 修正抽象化(Refined Abstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义。
  • 实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。
  • 具体实现化(Concrete Implementor)角色:这个角色给出实现化角色接口的具体实现。

 ===================引用完毕============================

 

对照这个说明,来看看“画图”的情况

 

=========================原先的想法=========================

两个等级结构:
抽象化等级结构:图形。
实现化等级结构:用画笔画图。

 

抽象化角色:图形、画笔。
修正抽象化角色:各种图形,比如圆形,长方形,正方形,菱形等。
实现化角色:(用画笔)画图形。具体一点就是画出一个“点”。(当初学几何的时候老师教导我们:点动成线)。
具体实现化角色:各种画笔风格的实现,就是实现用铅笔画一个点的效果、用毛笔画出一个点的效果等等。

 

=========================修改后的想法=========================

两个等级结构:

抽象化等级结构:图形。
实现化等级结构:用画笔画图形。

 

抽象化角色:图形。
修正抽象化角色:各种图形,比如圆形,长方形,正方形,菱形等。
实现化角色:(用画笔)画图形。具体一点就是画出一个“点”。(当初学几何的时候老师教导我们:点动成线)。
具体实现化角色:各种画笔风格的实现,就是实现用铅笔画一个点的效果、用毛笔画出一个点的效果等等。

 

 

 

 


图形和笔的关系
  图形是一个抽象概念,长方形也是一个抽象概念。假设我不知道什么是长方形,那么您要解释的话,恐怕要费不少口舌,那么最直接的办法就是,用笔画出来一个长方形,这样就有了一个感性的认识。


  长方形通过笔实现(画)了出来。图形想要实现出来就要通过笔,但是图形有好多种分类,笔也有很多种类。如果参合到一起,那就累了,所以需要分离开来,独立变化。


  图形要想实现出来,就得依赖笔,这就是一种强耦合,那么想办法把这种偶合解脱开,那么就是所谓的解耦了。


  抽象部分是什么?图形,实现部分是什么?把图形画出来。这里有点绕,也是桥接模式难于理解的地方。画图是由右面的笔来负责实现的,但是怎么画却是左面的图形来规定的。这个本来是分不开的,但是桥接模式就是要把他们给分离开。有难度才有挑战,才有必要创建一个模式来解决。
那么是如何分开的呢——组合。在图形里面定义一个笔的成员,然后在子类里面通过这个成员与笔进行合作。

 

  我的理解就是,用笔画图形就是抽象,而实现部分有两种不同的分类,一个是笔的分类:铅笔、毛笔、喷枪等;一个是图形的分类:直线、圆、长方形等。他们可以各自独立的变化,互补干扰。笔在扩展的时候,不需要考虑有多少种图形;图形在扩展的时候也不需要考虑是铅笔还是毛笔。

 

  问题似乎都解决的,但是对于左面的“图形”部分还有疑问,在《深入浅出设计模式》里面,桥接模式是用遥控器和电视机来举例子的,遥控器是抽象的,电视机是实现部分。思路应该是“遥控器控制电视转换频道,遥控器只是控制(下命令,没有实现),而具体的换频道的任务由电视机实现”。但是遥控器只画了一个子类,而且子类和父类里的函数名称有不尽相同。《深》并没有给出代码,所以这个我还没有理解透彻。

 

  不过想一想,可以“变化”就一定要用多个子类的方式实现吗?如果是这样的话,那么我的UML图是不是要改一下呢?

 

 

 

 

========================================

ps:

  标题为什么要用“纸上谈兵”呢?纸上谈兵就是说,理论上一套一套的,但是到了实际中就不行了。

  因为在我找到的例子中,有一部分例子是为了说明桥接模式而“构想”出来的,并不是真实的项目。您可以通过这些例子去了解、体会桥接模式,但是千万不要以为真实的代码就是这样的。所以我用了纸上谈兵这个成语。

  但是我并不是说这么做不对,而只是想说,看了这样的代码,可以学会桥接模式,但是到了实际中很可能还是不会用。最好的方法就是给出真实项目里的例子。但是这个难度确实很大,一到了真实项目里,就会遇到很多细节问题,从而影响对模式的理解,而且真实项目都带有一定的业务环境,首先要了解这个业务环境才能理解,这就又增加了难度。

  最后,解决一个问题往往有很多种方法,一提到具体问题,您可能就马上想到了另一种更好的解决方法,就会觉得他说的不好,从而就会阻碍对讲解的理解。

 

  不过我还是想尝试一下用真实的例子来解释一下桥接模式。下一篇就会详细说明。这里没写代码,我对伪代码不太擅长,下一篇会写真实的代码的。

 

 

 

 

2