您的位置:首页 > 其它

桥接模式

2016-01-02 23:04 197 查看
在软件系统中,某些类型由于自身的逻辑,它具有两个或两个以上的维度变化,那么如何应对这种“多维度的变化”呢?如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度呢?这就是即将要介绍的桥接模式(Bridge)。

使用频率:





medium

定义

桥接模式(Bridge),将抽象部分与它的实现部分分离,使它们都可以独立地变化。

意图

将抽象与实现解耦。

动机

当一种抽象类型可能有多种实现方式时,一般情况我们可以考虑使用继承来解决抽象类型的多种实现,在抽象类型中定义接口,而子类负责接口的具体实现。但这种 做法缺乏灵活性,由于抽象类型和子类之间紧紧地绑定在一起,使得这种关系在运行时不能再修改,这使得它难以修改、扩展和重用不利于抽象和实现解耦,而且这 也违背OOP原则:“优先使用对象聚集,而不是继承”。

结构图





图1 桥接模式(Bridge)

参与者

Abstraction

1. 定义抽象接口。

2. 拥有一个Implementor类型对象引用。

RefinedAbstraction

1. 扩展Abstraction中的接口定义。

Implementor

1. Implementor是具体实现的接口,Implementor和Abstraction接口并不一定完全一致(注:Proxy和ISubject接口 一一对应),实际上这两个接口可以完全不一样,Implementor提供具体操作方法,而Abstraction提供更高层次的调用。

ConcreteImplementor

1. 实现Implementor接口,给出具体实现。

1.1.2 正文

首先根据桥接模式(Bridge)的定义:将抽象部分与它的实现部分分离,但令我不解的是,怎样才能将抽象与其实现的具体方式分离呢?其实我的迷惑主要是 因为误解了实现的含义。这里实现指的是抽象类及其派生类用来实现自定的对象(而不是抽象类的派生类,这些派生类被称为具体类)。不过这样还是难以理解,现 在让我们通过具体的例子说明。

我们必需了解桥接模式(Bridge)存在的价值,然后推迟该模式,OK我们通过一个简单的例子说明为什么需要桥接模式(Bridge)吧!

从一个绘制形状的简单问题开始,假设我们接受一个任务:编写一个程序,使用两个绘图程序DrawProgram1和DrawProgram2之一绘制图形(矩形,圆形等),而且我们被告知,实例化图形的时候,它会知道应该使用绘图程序DP1还是DP2。

通过分析我们可以找出抽象类Shape,然后定义Retangle和Circle类继承抽象类Shape,还有就是绘图程序DP1和DP2。





图2绘图程序类图

我们现在初步定义了相关的方法和类,而把Shape,Retangle和Circle都定义为抽象类型方便以后扩展。但我们可以发现图形类型并没有与绘图程序关联起来,而且前面需求中提到





图3绘图程序类图

我们定义了Retangle1和Retangle2继承于抽象类Retangle,添加DrawLine()方法分别调用DP1和DP2的 DrawLine()方法,并且Circle1和Circle2中的实现基本相同。我们使用了一种直截了当的方法,实现了两种图形和两个绘图程序的关联。

现在Shape有四个具体类型(Retangle1,Retangle2,Circle1和Circle2),而且每个具体类型都和相应绘图程序对于,但 我们要记住“没有不变的需求,世上的软件都改动过三次以上,唯一一个只改动过两次的软件的拥有者已经死了,死在去修改需求的路上”,所以需求总是在不断的 变化之中,如果添加新的绘图程序DP3,那么我们就要增加两个具体类型,而且具体类型中调用DP3方法跟之前调用DP1、DP2并没有太大的区别(冗余问 题),现在有两种图形(Retangle和Cirle)和三个绘图程序(DP1,DP2和DP3),那么就拥有六种不同Shape(2种图形 * 3个绘图程序),如果我们继续扩展成三种图形那么就具有九种不同Shape(3种图形 * 3个绘图程序),这就会导致“类爆炸”问题。

上面的解决方法,因为抽象类型(Shape)和绘图程序之间是紧耦合,于是存在严重的“类爆炸”问题(每种形状都必须知道自己用哪个绘图程序)。我们需要一种方式将抽象上的变化和实现变化进行解耦。

将抽象与实现解耦这不就是桥接模式(Bridge)的意图吗?在介绍桥接模式(Bridge)之前,我们总结一下前面方式中的问题。

存在冗余

低内聚

紧耦合

由于前面的例子按照不同图形来进行继承的分类,如果我们按照不同绘图程序分类结果又如何呢?OK,那么让我们画出按照不同绘图程序分类类图。





图4绘图程序类图

现在我们继续使用四个类表示现有的图形的组合,但这里我们按照不同绘图程序派生不同图形,所以我们消除了图形类 (Retangle1,Retangle2, Circle1和Circle2)和绘图程序的之间的紧耦合,从而消除了它们之间的冗余。现在把耦合转移到更高的继承层次,但问题有出现了当有新的绘图程 序加入时,我们的确可以轻松地进行扩展,只要增加ShapeDP3类继承抽象类Shape就OK了,但是要实现一套一模一样的Retangle3和 Circle3了。

尽管这种方式对前面的方式有所改进,但冗余和耦合问题依然存在。

在我们每次使用设计模式时,我们应该根据设计原则去设计,而不是直接使用已有设计模式去套用,我们要明白的一点是设计模式是根据一定的设计原则而产生的。

这次我们要遵循两个基本原则:

首先我们可以很快的找出需求中变化:图形和绘图程序,然后我们使用抽象来封装变化就OK了。





图5封装绘图程序中变化

现在我们已经找出了变化图形和绘图程序,注意这里的OperationalDP1和OperationalDP2作为调用绘图程序(DP1和DP2)的接 口,因为DP1和DP2是两个已经存在的程序所为我们无法直接抽象出DP1和DP2的高层接口,通过一种间接方式抽象出高层接口,使用抽象类把变化封装在 它的“后面”,接着我们就是要在抽象类Shape和DrawingProgramming直接建立依赖关系了( 这里存在两种情形:

一、DrawProgramming类保存Shape对象引用

二、Shape类保存DrawProgramming对象引用

首先考虑第一种情形,如果在DrawProgramming保存Shape对象引用,我们通过调用Draw()方法绘制图形,但它们必需对Shape类中 的图形有所了解(Draw()方法将具体图形封装了),当我们使用OperationalDP1中的DrawCircle()方法时,就要知道Shape 中的Circle类型,这违反了对象应该只对自己负责。





图6 情形一

第二种情形,如果Shape对象使用DrawProgramming对象绘制图形时,图形无需知道具体绘图程序。当使用Circle中的Draw()方法 我们只需要调用DrawProgramming中的方法DrawCircle(),因此可以让Shape保存DrawProgramming的对象引用。





图6 情形二

通过分析我们可以确定采用情形二实现起来相对简单,接着在抽象类Shape增加DrawProgramming对象引用,从而在Shape和DrawProgramming之间建立了一种聚集关系(has – a关系)。

现在让我们回忆一下桥接模式(Bridge)的定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化。Shape类及其实现就是抽象部分,而 DrawingProgramming及其实现是具体部分,通过聚集使得它们分离开来,从而应对变化和扩展更加灵活。





图7 桥接模式实现绘图程序

我们已经完成了绘图程序的类设计,而且通过该程序类设计我们了解到了桥接模式(Bridge)的作用

通过上面的例子我们对桥接模式(Bridge)有了初步的了解,假设我们的绘图程序DP1拥有特有擦除方法Wipe(),从而需要在 OperationalDP1类中增加相应的方法,但没有必要修改DrawingProgramming类,现在问题出现了我们要在抽象类Shpe中增加 Wipe()方法,但这种修改是我们愿意看到的。这时我们可以考虑以下两种方法解决问题。

方法一:在基类中添加虚的方法,然后需要的子类重写基类的虚方法。

方法二:使用.NET Framework中的扩展方法,对基类方法进行扩展。

1.1.3 总结

桥接模式(Bridge)优点:

将实现予以解耦,让它和界面之间不再永久绑定。

抽象和实现可以独立扩展,不会影响到对方。

对于具体实现的修改,不会影响到客户端。

桥接模式(Bridge)缺点:

增加了设计复杂度。

抽象类的修改影响到子类。

桥接模式(Bridge)用途:

适用在需要跨多平台的图形和窗口系统。

当需要用不同的方式改变接口和实现时。

通过上述的介绍,我们了解为什么需要桥接模式(Bridge)和如何使用桥接模式(Bridge),由于对象的多维度的变化,使得难以决定变化时,我们可以把对象和变化抽象出来。

如果我们的对象依赖于抽象,对于具体的实现并不关心,我们可以通过对象组合,组合出我们想要的对象。桥接模式符合OCP( 1.Bridge模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。

2.所谓抽象和实现沿着各自维度的变化,即“子类化”它们,得到各个子类之后,便可以任意它们,从而获得不同路上的不同汽车。

3.Bridge模式有时候类似于多继承方案,但是多继承方案往往违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差。Bridge模式是比多继承方案更好的解决方法。

4.Bridge模式的应用一般在“两个非常强的变化维度”,有时候即使有两个变化的维度,但是某个方向的变化维度并不剧烈——换言之两个变化不会导致纵横交错的结果,并不一定要使用Bridge模式。

适用性:

在以下的情况下应当使用桥梁模式:

1.如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的联系。

2.设计要求实现化角色的任何改变不应当影响客户端,或者说实现化角色的改变对客户端是完全透明的。

3.一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦合。

4.虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。

总结:

Bridge模式是一个非常有用的模式,也非常复杂,它很好的符合了开放-封闭原则和优先使用对象,而不是继承这两个面向对象原则。

桥接模式与装饰的区别:

装饰模式:


这两个模式在一定程度上都是为了减少子类的数目,避免出现复杂的继承关系。但是它们解决的方法却各有不同,装饰模式把子类中比基类中多出来的
部分放到单独的类里面,以适应新功能增加的需要,当我们把描述新功能的类封装到基类的对象里面时,就得到了所需要的子类对象,这些描述新功能的类通过组合
可以实现很多的功能组合 .

桥接模式:

桥接模式则把原来的基类的实现化细节抽象出来,在构造到一个实现化的结构中,然后再把原来的基类改造成一个抽象化的等级结构,这样就可以实现系统在多个维度上的独立变化 。

UML类图



[b]详细代码请参考我的git:https://github.com/wzyxidian/DesignModel.git[/b]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: