设计模式(7) 桥接模式(BRIDGE)

问题聚焦:
    上一节讲了对象结构型模式,其主要的思想是对象的组合。并接触了一个典型的适配器模式。
    这一节再讲一个常用的桥接模式,其主要的思想是抽象部分与实现部分的分离,使得抽象部分与系统平台分离开来,从而实现可移植性。
    桥接主要指抽象部分和实现部分之间的关系。

意图:
    将抽象部分与它的实现部分分离,使它们都可以独立地变化。

别名:Handle/Body


动机:
    当一个抽象可能有多个实现时,通常的思路为继承出多个功能类似又各司其职的子类。
    继承的方法的缺点是:抽象部分与它的实现部分过分紧密,使得难以对抽象部分和实现部分独立地进行修改、扩充和重用。
    考虑下面的这个例子:
    一个用户界面工具箱,可移植。同时支持X Window System和IBM的PM系统。运用继承机制,我们可以定义Window抽象类和它的两个子类XWindow与PMWindow,由它们分别实现不同系统平台上的Window界面。
    这个实现的不足之处在于:
    1) 扩展Window抽象使之适用于不同种类的窗口或新的系统平台很不方便。
        假设Window有一个子类IconWindow专门负责图标的处理,为了支持两种平台,必须实现两个新类XIconWindow和PMIconWindow。即如果我们需要支持各种功能的组件,不得不为每种组件实现两种子类。
    2)继承机制使得客户代码与平台相关。结果就是,客户代码同时也要依赖当前的平台,会使得客户代也很难移植到其他平台上去。
    改进目标:客户在创建代码时,不涉及到其具体实现部分,仅仅是窗口的实现部分依赖于应用运行的平台。这样,客户代码在创建窗口时就不涉及到特定的平台了。
    用桥接模式解决这个问题。
  •     将Window抽象和它的实现部分分别放在独立的类层次结构中。
  •     其中一个类层次结构针对窗口接口。
  •     另一个独立的类层次结构针对平台相关的窗口实现部分,这个类层次结构的根类为WindowImp。
    对Window子类的所有操作都是用WindowImp接口中的抽象操作实现。这就将窗口的抽象与系统平台相关的实现部分分离开来。因此,我们将Window与WindowImp之间的关系称之为桥接。
    设计模式(7)  桥接模式(BRIDGE)设计模式(7)  桥接模式(BRIDGE)


适用性:
一下情况使用Bridge模式:
  • 你不希望在抽象和它的实现部分之间有一个固定的绑定关系。如在程序运行时刻实现部分应可以被选择或者切换。
  • 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时,桥接模式使你可以对不同的抽象接口和实现部分进行组合,并分别扩充。
  • 对一个抽象的实现部分的修改应对客户不产生影响,即客户的挨骂不必重新编译。
  • 你想对客户完全隐藏抽象的实现部分,在C++中,类的表示在类接口中是可见的。
  • 想在多个对象间共享实现,但同时要求客户并不知道这一点。
结构:
设计模式(7)  桥接模式(BRIDGE)
设计模式(7)  桥接模式(BRIDGE)
参与者:
  • 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;
}

相关模式:
抽象工厂模式可以用来创建和配置一个特定的桥接模式。
适配器模式用来帮助无关的类协同工作。
桥接模式使得抽象接口和实现部分可以独立进行改变。


参考资料:
《设计模式:可复用面向对象软件的基础》


设计模式(7) 桥接模式(BRIDGE)

上一篇:Ubuntu 13.10 英文版本 Emacs 24.3中文输入


下一篇:HDU4614Vases and Flowers 二分+线段树;