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

来源:互联网 发布:知乎怎么发表想法 编辑:程序博客网 时间:2024/05/12 02:05
问题聚焦:
    上一节讲了对象结构型模式,其主要的思想是对象的组合。并接触了一个典型的适配器模式。
    这一节再讲一个常用的桥接模式,其主要的思想是抽象部分与实现部分的分离,使得抽象部分与系统平台分离开来,从而实现可移植性。
    桥接主要指抽象部分和实现部分之间的关系。
    补充一个实例。

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

别名: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;}

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

Demo:
/* * 桥接模式demo * 现实里如果你想要一个android手机,又想要一个iphone的话,只能掏出钱包买两个手机啦,这个可以理解为子类继承 * 但是如果用了桥接模式,就可以在同一个手机安装android系统,或者IOS系统,同一个手机,不同实现,这就是桥接模式*/#include <iostream>class TelephoneImp{public:TelephoneImp() {}virtual void produce() = 0;};class Telephone{public:Telephone() {  };void install(TelephoneImp* imp) {_phoneimp = imp;_phoneimp->produce(); };private:TelephoneImp* _phoneimp;};//----------------------------------------------------------class AndroidOS: public TelephoneImp{public:AndroidOS() : TelephoneImp(){}void produce() { std::cout << "You have an Android phone now!" << std::endl;};};class IphoneOS : public TelephoneImp{public:IphoneOS():TelephoneImp(){}void produce(){std::cout << "You have an Iphone now!" << std::endl;}};int main(){std::cout << "-----------------------" << std::endl;std::cout << "      桥接模式Demo" << std::endl;std::cout << "-----------------------" << std::endl;AndroidOS* androidos = new AndroidOS();IphoneOS* iphoneos = new IphoneOS();Telephone* myphone = new Telephone();std::cout << "Installing android os..." << std::endl;myphone->install(androidos);std::cout << std::endl;std::cout << "Installing ios ..." << std::endl;myphone->install(iphoneos);system("Pause");return 0;}

运行截图:


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


1 0
原创粉丝点击