问题聚焦:
上一节讲了对象结构型模式,其主要的思想是对象的组合。并接触了一个典型的适配器模式。
这一节再讲一个常用的桥接模式,其主要的思想是抽象部分与实现部分的分离,使得抽象部分与系统平台分离开来,从而实现可移植性。
桥接主要指抽象部分和实现部分之间的关系。
意图:
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
别名:Handle/Body
动机:
当一个抽象可能有多个实现时,通常的思路为继承出多个功能类似又各司其职的子类。
继承的方法的缺点是:抽象部分与它的实现部分过分紧密,使得难以对抽象部分和实现部分独立地进行修改、扩充和重用。
考虑下面的这个例子:
一个用户界面工具箱,可移植。同时支持X Window System和IBM的PM系统。运用继承机制,我们可以定义Window抽象类和它的两个子类XWindow与PMWindow,由它们分别实现不同系统平台上的Window界面。
这个实现的不足之处在于:
1) 扩展Window抽象使之适用于不同种类的窗口或新的系统平台很不方便。
假设Window有一个子类IconWindow专门负责图标的处理,为了支持两种平台,必须实现两个新类XIconWindow和PMIconWindow。即如果我们需要支持各种功能的组件,不得不为每种组件实现两种子类。
2)继承机制使得客户代码与平台相关。结果就是,客户代码同时也要依赖当前的平台,会使得客户代也很难移植到其他平台上去。
改进目标:客户在创建代码时,不涉及到其具体实现部分,仅仅是窗口的实现部分依赖于应用运行的平台。这样,客户代码在创建窗口时就不涉及到特定的平台了。
用桥接模式解决这个问题。
- 将Window抽象和它的实现部分分别放在独立的类层次结构中。
- 其中一个类层次结构针对窗口接口。
- 另一个独立的类层次结构针对平台相关的窗口实现部分,这个类层次结构的根类为WindowImp。
对Window子类的所有操作都是用WindowImp接口中的抽象操作实现。这就将窗口的抽象与系统平台相关的实现部分分离开来。因此,我们将Window与WindowImp之间的关系称之为桥接。
适用性:
一下情况使用Bridge模式:
- 你不希望在抽象和它的实现部分之间有一个固定的绑定关系。如在程序运行时刻实现部分应可以被选择或者切换。
- 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时,桥接模式使你可以对不同的抽象接口和实现部分进行组合,并分别扩充。
- 对一个抽象的实现部分的修改应对客户不产生影响,即客户的挨骂不必重新编译。
- 你想对客户完全隐藏抽象的实现部分,在C++中,类的表示在类接口中是可见的。
- 想在多个对象间共享实现,但同时要求客户并不知道这一点。
参与者:
- Abstraction(Window):定义抽象类的接口;维护一个指向Implementor类型对象的指针;
- RefinedAbstraction(IconWindow):扩充由Abstraction定义的接口
- Implementor(WindowImp):定义实现类的接口,提供基本操作,Abstraction则定义了基于这些基本操作的较高层次的操作。
- ConcreteImplementor(XwindowImp,PMWindowImp):实现Implementor接口并定义它的具体实现。
Abstraction将client的请求转发给它的Implementor对象。
效果:主要说明桥接模式的优点
- 分离接口及其实现部分:抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现
- 提高可扩充性
- 实现细节对客户透明
使用桥接模式时,需要注意以下一些问题
1 仅有一个Implementor:在仅有一个实现的时候,没有必要创建一个抽象的Implementor类。
2 创建正确的Implementor:
当存在多个Implementor类的时候,你应该用何种方法,在何时何处确定创建哪一个Implementor类呢?
如果Abstraction知道所有的ConcreteImplementor类,它就可以在它的构造器中对其中的一个类进行实例化,它可以通过传递给构造器的参数确定实例化哪一个类。另外一种方法是首先选择一个缺省的实现,然后根据需要改变这个实现。
也可以代理给另一个对象,由它一次决定。在Window/WindowImp例子中,我们可以引入一个factory对象,该对象的唯一职责就是封装系统平台的细节,这个对象知道应该为所用的平台创建何种类型的WindowImp对象,Window仅需要向它请求一个WindowImp,而它会返回正确类型的WindowImp对象。这种方法的有点是,Abstraction类不和任何一个Implemetor类直接耦合。
3 共享Implementor对象:引入引用计数器机制。
4 采用多重继承机制:在C++中可以使用多重继承机制将抽象接口和它的实现部分结合起来。
代码示例:
下面的代码实现上面的Window/WindowImp的例子。
Window类为客户应用程序定义了窗口抽象类。
class Window { public: Window(View*contents); // requests handled by window virtual void DrawContents(); virtual void Open(); virtual void Close(); virtual void Iconify(); virtual void Deiconify(); // requests forwarded to implementation virtual void SetOrigin(const Points& at); virtual void SetExtent(const Points& extent); virtual void Raise(); virtual void Lower(); virtual void DrawLine(const Point&, const Point&); virtual void DrawRect(const Point&, const Point&); virtual void DrawPolygon(const Point[], int n); virtual void DrawText(const char*, const Points&); protected: WindowImp* GetWindowImp(); View* GetView(); private: WindowImp* _imp; View* _contents; };
Window维护一个对WindowImp的引用,WindowImp抽象类定义了一个对底层窗口系统的接口
class WindowImp { public: virtual void ImpTop() = 0; virtual void ImpBottom() = 0; virtual void ImpSetExtent(const Point&) = 0 ; virtual void ImpSetOrigin(const Point&) = 0; virtual void DeviceRect(Coord, Coord, Coord, Coord) = 0; virtual void DeviceText(const char*, Coord, Coord) = 0; virtual void DeviceBitmap(const char*, Cood, Coord) = 0; // lots more functions for drawing on windows... protected: WindowImp(); };
Window的子类定义了应用差您工序可能用到的不同类型的窗口,如应用窗口,图标,对话框临时窗口,以及工具箱的移动面板等。
如ApplicationWindow类将实现DrawContents操作以绘制它所存储逇View示例:
class ApplicationWindow : public Window { public: virtual void DrawContents(); }; void ApplicationWindow::DrawContents() { GetView()->DrawOn(this); }
又如,IconWindow存储了它所显示的图标对应的位图名。(代码省略)
我们还可以定义许多其他类型的窗口类。
Window的操作由WindowImp的接口定义。
例如,在调用WidowImp来操作在窗口中绘制矩形之前,DrawRect必须从它的两个Point参数中提取四个坐标值。
void Window::DrawRect(const Point& p1,const Point& p2) { WindowImp* imp = GetWindowImp(); imp->DeviceRect(p1.X(), p1.Y(), p2.X(), p2.Y()); }
具体的WindowImp子类可支持不同的窗口系统。XwindowImp子类支持XWindow窗口系统。
class XWindowImp : public WindowImp { public: XWindowImp(); virtual void DeviceRect(Coord, Coord, Coord, Coord); private: // lots of X Window system-specific state, including: Display* _dpy; Drawable _winid; GC _gc; };
对于PM系统,我们定义PMWindowImp类。(代码省略)
这些子类用窗口系统的基本操作实现WindowImp的操作
如,X窗口系统这样实现DeviceRect:
void XWindowImp::DeviceRect() { int x = round(min(x0, x1)); int y = round(min(y0, y1)); int w = round(abs(x0 - x1)); int h = round(abs(x0 - x1)); XDrawRectangle(_dpy, _winid, _gc, x, y, w, h); }
而PM对相同操作的实现可能完全不一样,这使基于平台的实现。
那么一个窗口怎样得到正确而WindowImp子类的实例呢?
可以在Window的GetWindowImp操作中,从一个抽象工厂得到一个正确的实例。
WindowImp * Window::GetWindow() { if (_imp == 0) { _imp = WindowSystemFactory::Instance()->MakeWindowImp(); // WindowSystemFactory::Instance()函数返回一个抽象工厂 // 该工厂负责处理所有与特定窗口系统相关的对象 // 同时,这个抽象工厂实现称了一个单件(单例模式) } return _imp; }
相关模式:
抽象工厂模式可以用来创建和配置一个特定的桥接模式。
适配器模式用来帮助无关的类协同工作。
桥接模式使得抽象接口和实现部分可以独立进行改变。
参考资料:
《设计模式:可复用面向对象软件的基础》