桥接(Bridge)模式理解

 

桥接(Bridge)模式理解

 

       在四人团的著作《设计模式》中,对桥接模式的意图只用了一句话:将抽象部分与它的实现部分分离,使它们都可以独立的变化。

 

       理解这句话需要细细分析一番:

       主要是理解“抽象部分与它的实现部分分离”。怎么理解呢?这里可以提出至少这样三个问题:

Ø         什么是抽象部分?

Ø         什么是实现部分?

Ø         怎么分离?

       为了回答上面的五个问题,这里先引出一个例子:有这样一个个人信息管理软件,其中有一个生成一些文档功能,有两种文档:个人简历,邮件;这些文档有两种风格:现代型风格,典雅型风格。(为了便于表述,姑且将这个例子叫做Document案例吧)

       以常规的方式建模时,针对支持两种文档类型,如图1

 

1

       OK,这个设计毫无问题。接下了就是去支持那两种风格了,于是新的设计瞬时间产生了(简单嘛,接着继承不就得了吗,费多大事干嘛),如图2

2  传统模式下设计的Document案例

       很好!到现在我还在为这个设计沾沾自喜,认为这个设计毫无问题,因为之前很多人都是这么做的,而且设计产生得如此的快。

       但是,老天总是风云不定,用户总是变化莫测的。这时,用户跟我说,我现在要增加点功能:文档种类增加一个名片,风格要增加一种叫做“专业型(Professional)”的风格。

       好的,我加。于是便开始着手加新的设计:步骤也很自然,先加一个叫Callingcard的类。接下来再为ResumeMailCallingcard逐个增加支持专业型(Professional)风格的子类,结果设计成了如图3所示:

3

其中,红色则是为了支持名片(Calling card)而增加的类;绿色标示的类是为了支持专业型(Professional)风格而增加的类。总共添加了六个类。此时,看到如此多的新增类,我意识到如果用户想支持更多的文档类型和更多的风格,那么类的增加将会更多,这个增加是爆炸性的。怎么办呢?我想唯一的答案就是重新设计。

怎么设计呢!翻开《设计模式》看看吧,里面一定有我们想要的答案。没错,其中有个叫“桥接(Bridge)”的模式,比较符合这个问题。

 

现在让我们用设计模式的观点来重新考查一下需求吧。说到这儿,不得不说说设计模式中的两个概念:抽出相同点,封装变化点。

什么是变化点呢?在我们的Document案例中,前面提到的用户要增加支持名片和专业风格,就是变化的根源。也就是说这里就有两个变化点:其一,文档的种类可变;其二,文档的风格可变。根据设计四人团的观点,既然有了变化点,那就封装它们吧。

怎么封装呢?这里就涉及到“抽出相同点”这个概念了。所谓“万变不离其宗”,这里的这个“宗”,其实是指的当变化点在其千变万化的过程中,存在的始终没有变,或者说始终变不了的一些特性和行为。我们要把这些永恒(相对永恒)不变的东西找出来,这就是“抽出相同点”的概念了。“抽出相同点”的目的是什么呢?我认为至少有个目的就是“封装变化点”。封装的过程就是将相同点抽象成一个父类,而所有的变化就用它的各个子类来完成。Document案例中的两个变化点,文档种类变化和文档风格变化。可以用如图4和图5的结构来封装:

 

4  Document封装了多种类型文档

 

 

5  DrawElement 封装了多种风格

       这里需要说明一下为什么图5DrawElement封装了多种风格呢。这就要分析什么东西决定了风格了。在这里,分析的初步结果就是:文档的背景,文字,和一些线条决定了整个文档的风格。

 

       说那么多“封装变化点”干什么,我们不是在讨论怎样用“桥接模式”解决Document案例中出现的类爆炸问题吗,是不是有些跑题了?当然没有。为何?请看下文。

       现在,我们该来看看文章开头提出的几个问题了:

Ø         什么是抽象部分?

Ø         什么是实现部分?

Ø         怎么分离?

第一个问题和第二个问题中提及的抽象和实现与通常说的抽象和现象有点不同,父类接口是抽象,而子类实现接口是实现,这是我一直理解的抽象和实现的概念。然而,理解这里(“桥接模式”)的抽象和实现,要和“分离”这个词一起来看,如图6

                                

                         a                                                         b

6 a)没有将实现分离

b)将实现分离到Implement

a)没有将实现分离,谈抽象和实现的意义不大。(bAbstract的操作是靠调用Implement的操作来实现,实质性的工作是在Implement中实现的。在这里,如果没有分离就谈不上什么抽象和实现了(个人观点)。

       现在回答“怎么分离?”就涉及到“封装变化点”了。在“桥接模式”中,分离就是将变化点封装起来,作为Implement以图6 中(b)的方式分离出来。

对于Document案例,可以将风格可变这点分离出来,结果,如图7

7  用“桥接模式”设计的Document案例

 

当然,也可以将“类型可变”这个变化点也分离出来。但这里没这个必要,因为总共只有两个变化点,分出去一个的同是另一个也已分离出来了呀。

注意:在《设计模式》一书在回答分析“参与者”之“ImplementorWindowImp)”参与者时有这样一句话“……。一般来讲,Implementor接口仅提供基本操作,而Abstraction则定义了基于这些基本操作的较高层次的操作。”认真理解下这句话,我认为可以更好的理解所谓实现和抽象。在Document案例中,可能未必将这句话体现的淋漓尽致,因为Document的抽象接口DrawGroundback(),DrawText()和DrawLine()仅仅是对DrawElement的方法的简单包装;但是如果在Document中增加一格函数DrawPolygon()用于画多边形的话DrawElement::DrawLine()不就成了基本操作了。理解这句话是有助于看清“桥接模式”和“策略模式”的区别的。有鉴于此,我觉得将“风格可变”的实现分离出来,更为合适些。

现在,我们再来增加文档类型支持和风格支持,简直是太容易了,看看结果吧,如图8

8  增加名片和专业风格支持

由这个过程,不难看出用“桥接模式”比起完全用继承的方法来实现“变化”是要简单得多了。

 
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值