Bridge (桥)模式
GoF :将抽象部分与它的实现部分分离,使它们都可以独立地变化。
解释一下 GoF 的 定义:就是指将抽象部分和实现部分分开,让它们各自随意增加减少。而不受其它约束。
下面引自《 Thinking in Patterns with Java 》:
Bridge 模式实际上就是一个组织代码的工 具,它使得你可以添加任意数量的新的前端服务,而这些前端服务又可以通过把这些操作委托给任意数量的后端对象来实现。通过使用Bridge 模式,你可以避免(类)组合所带来的类的数目的爆炸性增长。但是别忘了Bridge 模式所处理的一系列变化通常都是发生在编码阶段:当为了实现某个功能而必须处理越来越多的选项(options )时,Bridge 模式可以使你的代码 保持条例性。
通常,当一个抽象类(或接口)有多个实现(派生类),这些实现(派生 类)之间可能有以下两种情况:
1. 这些实现之间是并列的关系。如:我们要实现各类动物的睡觉方式,蝙蝠 是倒挂着睡觉而狗是爬着睡觉。这两种动物的睡觉方式是并列的,没有概念上的重复。这时候我们使用继承就可以解决问题了。即使有更多种的动物,继承也可以自 如的应付。
2. 实际应用上 , 常常有 可能在这多个实现类之间有概念上重叠 . 那么需要我们把抽象共同部分和行 为共同部分各自独立开来 , 原来是准备放在一个接口里 , 现在需要设计两个接口 , 分别 放置抽象和行为。
Bridge 模式适用于“一个抽象部分拥有不同的实现部分”的情况。“让抽象部分和实现部分可以独立地变化”意味 着我可以在不改变实现部分的前提下为抽象部分增加新的内容,反之亦然。
下面举一个通俗的例子来说明 Bridge 模式的运用:
假如我们的系统中需要实现一个画图的功能(这样的需求是真实存在的,我的前一家公司做了个票务输出软 件,其中就有画图的功能,那个软件使用 C# 实现的,图画的非常漂亮)。现在需要画一个圆,圆有两种要求:一种“彩色图”,一种“无彩图”。我 们的传统做法是写出一个抽象类 Circle 然后派生出类两个类,这样的做法是非常正确的,也符合 OO 思想。但他只符合我们上面所说的第一种情况。其他情况可能就不适应了,比如说“彩色图”分大小,那么 就会产生四个图形(也就是四个类):大彩色图,大无彩图,小彩色图,小无彩图。这时候你可能会说再从抽象类 Circle 继承不就完了么?但是,如果现在有画矩形的要求怎么办呢?再定义一个 Rectangle 类,来四个继承?或者是干脆定义一个 Shape 让 Circle 和 Rectangle 两个类来继承然 后再分别派生类?如果我要增加其他图形呢?我们的类成核裂变式的增长,麻烦吗?再说我们的整个系统容许我们在增加新的类层次么?回答肯定是否定的。怎么办 呢? Bridge 模式可以帮我们轻松搞定。
我们上面说了: Bridge 模式是将抽象部分与它的实现部分分离,使它们都可以独立地变化。
先实现抽象部分:
package Bridge;
public abstract class DrawGraph
{
private Colourful colour; // 彩色
public void setColour() // 设置所需要的彩 色
{
this.colour = GpColorSingleton.getTheColor();
}
public Colourful getColour() // 得到 所需要的彩色
{
return colour;
}
public abstract void drawGp(); // 抽 象方法 , 待派生类自己实现
}//end abstract class DrawGraph
package Bridge;
public abstract class Colourful
{
public abstract void drawColourfulGp();
//do something....
}//end class Colourful
这里的抽象部分是可以随便加的,如果你需要 3D 效 果也可以加一个抽象的 3D 类。在后面的实现部分,将此 3D 类实 现出来就可以。
下面实现派生类部分:
package Bridge;
public class DrawBigCircle extends DrawGraph
{
public DrawBigCircle()
{
this.setColour();// 继承而来
}// end DrawCircle
public void drawGp()
{
Colourful color = this.getColour();// 继 承而来
color.drawColourfulGp();// 决定是否彩色
}//end drawGp
}//end class DrawCircle
package Bridge;
public class DrawSmallCircle extends DrawGraph
{
public DrawSmallCircle()
{
this.setColour();// 继承而来
}// end DrawCircle
public void drawGp()
{
Colourful color = this.getColour();// 继 承而来
color.drawColourfulGp(); // 决定是否彩色
}//end drawGp
}
上面两个类实现了图形的画法。下面是是否加颜色的设置。
package Bridge;
public class ColorGp extends Colourful {
public ColorGp()
{
//do something....
}//end ColorGp()
public void drawColourfulGp()
{
System.out.println(" 彩色图形 ");
}//end drawColourfulGp()
}//end class ColorGp
package Bridge;
public class UnColorGp extends Colourful {
public UnColorGp()
{
// do something....
}//end UnColorGp()
public void drawColourfulGp()
{
System.out.println(" 无彩 -- 图形 ");
}//end drawColourfulGp()
}//end class UnColorGp
实现部分完成了,如果你在抽象部分家了 3D 的 要求,那么在实现部分也要相应得实现出来。下面用一个 单态类来 hold 我们的 Colourful 类:
package Bridge;
public class GpColorSingleton
{
private static Colourful colorful;
public GpColorSingleton(Colourful colorful)
{
this.colorful = colorful;
}//end GpColorSingleton(...)
public static Colourful getTheColor()
{
return colorful;
}//end getTheColor()
}//end class GpColorSingleton
最后我们给出一个实现看看,彩色图和无彩图是怎么画出来的:
package Bridge;
public class BridgePattern
{
GpColorSingleton gpColorSingleton = new GpColorSingleton(new ColorGp());
DrawBigCircle bigCircle = new DrawBigCircle();
GpColorSingleton gpUnColorSingleton = new GpColorSingleton(new UnColorGp());
DrawSmallCircle smallCircle = new DrawSmallCircle();
public void play()
{
bigCircle.drawGp(); // 画出大的彩色圆
smallCircle.drawGp(); // 画出小的无彩圆
}//end play()
public static void main(String[] args)
{
BridgePattern bp = new BridgePattern();
bp.play();
}//end main(...)
}//end class BridgePattern
这样我们完成了 Bridge 模式的实现。在整个的学习过程中,我们所要具备的知识是,类的继承、多态。最重要的是要理解 GoF 给出的定义: 将抽象部分与它的实现部分分离,使它们都可以独立地变化。
下面是 Bridge 模式的 UML 图(以我们给出的例子为例):
适用性: (参考《设计模式》第 101 页)
l 你不希望在抽象和它的实现部分之间有一个固定的邦定关系。
l 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这是的 Bridge 模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
l 对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。
Bridge 模式的关键特征:
l 意图:将一组实现部分从另一组使用他们的对象中分离出来。
l 问题:一个抽象类的派生类必须使用多种实现部分,但又不能引起类数量的爆炸。
l 解决方案:为所有的实现部分定义一个接口,让抽象类的所有派生类使用这个接口。
l 参与者与协作者: Abstraction 为正在实现的对象定义接口。 Implementor 为特定的实现部分类定义接口。 Abstraction 的派生类使用 Implementor 的派生类,而不必知道自己使用的特定 ConcreteImplementor
l 效果:“实现部分与使用它的对象的分离”增加了灵活性。客户对象不需要了解实现问题。
l 实现:将实现部分封装在一个抽象类中;在被实现的抽象部分基类中包含一个实现部分基类的句柄。