设计模式之Bridge(1)

来源:互联网 发布:linux常用命令chmod 编辑:程序博客网 时间:2024/06/02 05:54
一、概述
Bridge(桥接)模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。

二、结构
Bridge模式的结构如下:
 
1:Bridge模式类图示意
由于Bridge实现抽象-实现的特性,它与Builder模式存在一定的相似性,但二者的区别也是十分显著的,后者更专注于复杂对象的创建,可以认为是Bridge模式的在对象创建方面的一个应用。
Bridge模式与Object Adapter模式及后面即将讲到的Facade模式等Structural Patterns也存在一定的相似性。Bridge模式与Facade模式的区别比较明显,二者的意图完全不同,前者通过Delegate实现抽象-实现的分离,而后者通过封装已有多个子系统来简化接口;而Bridge模式与Object Adapter模式的也主要表现的意图上,Adapter模式的目的在于接口转换,虽然对于Object Adapter而言,具体的实现被委托给了具体的adaptee类。此外,Adapter与Adaptee往往不具有相同的接口(要不然何来转换的必要),而Bridge模式下,Implementor与ConcreteImplementor属于统一类系,并且,前者标识了后者对外的接口;同样,对于Facade类而言,它与被封装的子系统之间往往也没有继承关系。

三、应用
在以下几种情况下可以考虑使用Bridge模式:
Case 1、你不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。
Case 2、类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时Bridge模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
Case 3、对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。
Case 4、(C++)你想对客户完全隐藏抽象的实现部分。在C++中,类的表示在类接口中是可见的。
Case 5、有许多类要生成。这样一种类层次结构说明你必须将一个对象分解成两个部分。Rumbaugh称这种类层次结构为“嵌套的普化”(nested generalizations)。
Case 6、你想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点。一个简单的例子便是Coplien的String类,在这个类中多个对象可以共享同一个字符串表示(StringRep)(见附录)。
与Builder模式一样,Bridge模式也是3D原则(见笔记1:概述)在设计中的应用,因此他们具有一定的相似性,但Builder专注与复杂对象的创建,而Bridge模式则主要强调Delegate,即职责的委派。
Case 1在很多界面方案切换程序中被大量使用(如使用单独的界面绘制类,在其中实现同一界面方案下不同控件的色彩、阴影等绘制方法,每一种界面元素都保存或者可以间接获取到该绘制类的指针,从而可以在实际绘制自身时加以运用。感兴趣的朋友可以参考著名界面库BCGPro的CBCGPVisualManager及其派生类的实现);同样,Case 1在许多需要考虑跨平台(可能是OS,也可能是所使用的第三方应用系统,如数据库)问题的应用中也被大量用到,只不过,在这些情况下,实际被编译到最终应用中的可能只是多个Implementor中的一个。
以上几种Case中,前5种主要表现的是由Delegate所带来的分别修改而不相互影响等优点,而Case 6所提出的共享实现是说,我们可以通过将大家公用的部分提取成单独的Implementor,只在各SpecificAbstraction中进行更深入的对Abstraction的细化,同时通过组合/继承将部分操作交给Implementor完成。

四、优缺点
Bridge模式有以下一些优点:
1
)分离接口及其实现部分 一个实现未必不变地绑定在一个接口上。抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现。
将Abstraction和Implementor分离有助于降低对实现部分编译时刻的依赖性,当改变一个实现类时,并不需要重新编译Abstraction类和它的客户程序。为了保证一个类库的不同版本之间的二进制兼容性,一定要有这个性质。
另外,接口与实现分离有助于分层,从而产生更好的结构化系统,系统的高层部分仅需知道Abstraction和Implementor即可。
2
)提高可扩充性 你可以独立地对Abstraction和Implementor层次结构进行扩充。
3
)实现细节对客户透明 你可以对客户隐藏实现细节,例如共享Implementor对象以及相应的引用计数机制(如果有的话)。

五、举例
Bridge模式的优点只有在存在多个RefinedAbstraction、ConcreteImplementor的情况下才比较突出,但很多程序库,为了简化接口类,同时出于扩展的需要,往往也将类与其实现分离成独立的两个类,这在STL、boost中可以说是屡见不鲜。对于我们的普通应用,在设计时也应考虑为以后的扩展保留一定余地,特别是在设计那些基础类时尤其需要注意。
在MFC中,典型的Bridge模式的应用如CArchive与CFile(分别代表类图中的Abstraction与Implementor),CArchive对外提供读写对象的接口,而CFile及其子类负责提供不同的数据保存机制,如读写内存,读写磁盘文件,读写Socket等。在构造CArchive对象时向其传递一个CFile(或其子类)的对象,使得前者可以获取必要的序列化相关信息,客户代码在进行实际的序列化操作时,可以完全不用去考虑CFile类是如何进行读写的,这种分离实现的结果不但简化了各自对外的接口,也使得对Abstraction和Implementor进行扩展变得十分方便。
下面是一个典型的应用Bridge模式的例子,两种不同的时间表示(不同的RefinedAbstraction)拥有相同的接口(拥有相同的基类Time),但内部分别使用了两个不同的时间实现(不同的ConcreteImplementor),这种设计使得Time及其子类的接口比较简单,同时也使得重用TimeImp成为可能,而要对该实现进行扩展,只需要实现新的RefinedAbstraction以及ConcreteImplementor即可:

#include <iostream.h>
#include <iomanip.h>
#include <string.h>

class
 TimeImp {
public
:
   TimeImp( int hr, int min ) {
      hr_ = hr;  min_ = min; }
   virtual
 void tell() {
      cout << "time is " << setw(2) << setfill(48) << hr_ << min_ << endl; }
protected
:
   int
 hr_, min_;
};


class
 CivilianTimeImp : public TimeImp {
public
:
   CivilianTimeImp( int hr, int min, int pm ) : TimeImp( hr, min ) {
      if
 (pm)
         strcpy( whichM_, " PM" );
      else

         strcpy( whichM_, " AM" ); }
   /* virtual */
 void tell() {
      cout << "time is " << hr_ << ":" << min_ << whichM_ << endl; }
protected
:
   char
  whichM_[4];
};


class
 ZuluTimeImp : public TimeImp {
public
:
   ZuluTimeImp( int hr, int min, int zone ) : TimeImp( hr, min ) {
      if
 (zone == 5)
         strcpy( zone_, " Eastern Standard Time" );
      else if
 (zone == 6)
         strcpy( zone_, " Central Standard Time" ); }
   /* virtual */
 void tell() {
      cout << "time is " << setw(2) << setfill(48) << hr_ << min_ 
         <<
 zone_ << endl; }
protected
:
   char
  zone_[30];
};


class
 Time {
public
:
   Time() { }
   Time( int hr, int min ) {
      imp_ = new TimeImp( hr, min ); }
   virtual
 void tell() {
      imp_->tell(); }
protected
:
   TimeImp*  imp_;
};


class
 CivilianTime : public Time {
public
:
   CivilianTime( int hr, int min, int pm ) {
      imp_ = new CivilianTimeImp( hr, min, pm ); }
};


class
 ZuluTime : public Time {
public
:
   ZuluTime( int hr, int min, int zone ) {
      imp_ = new ZuluTimeImp( hr, min, zone ); }
};


void
 main() {
   Time*  times[3];
   times[0] = new Time( 14, 30 );
   times[1] = new CivilianTime( 2, 30, 1 );
   times[2] = new ZuluTime( 14, 30, 6 );
   for
 (int i=0; i < 3; i++)
      times[i]->tell();
}


// time is 1430
// time is 2:30 PM
// time is 1430 Central Standard Time

附:
Coplien的String及StringRef实现
#include <stdio.h>
#include <string.h>

class
 StringRep
{

    friend class
 String;

public
:
    char
* text;
    int
 refCount;
    
    StringRep()
    {
        *(
text = new char[1]) = '/0';
    }

    
    StringRep( const StringRep& s )
    {

        strcpy( text = new char[strlen(s.text) + 1], s.text);
    }

    
    StringRep( const char* s )
    {

        strcpy( text = new char[strlen(s) + 1], s);
    }

    
    StringRep( char** const r)
    {

        text = *r;
        *
r = 0;
        refCount = 1;
    }
    
    ~
StringRep()
    {

        delete
[] text;
    }

    
    int
 length() const
    {

        return
 strlen( text );
    }

    
    void
 print() const
    {

        printf("%s/n", text);
    }
};


class
 String
{

    friend class
 StringRep;

public
:
    StringRep* operator->() const
    {

        return
 imp;
    }

    
    String()
    {
        (
imp = new StringRep())->refCount = 1;
    }

    
    String(const char* charStr)
    {
        (
imp = new StringRep(charStr))->refCount = 1;
    }

    
    String operator=(const String& q)
    {

        imp->refCount--;
        if
 (imp->refCount <= 0 && imp!= q.imp)
            delete
 imp;
        
        imp = q.imp;
        imp->refCount++;
        return
 *this;
    }
    
    ~
String()
    {

        imp->refCount--;
        if
 (imp->refCount <= 0)
            delete
 imp;
    }


private
:
    String(char** r)
    {

        imp = new StringRep(r);
    }

    
    StringRep* imp;
};


// Using Counter Pointer Classes
int main() {
    String a( "abcd" );
    String b( "efgh" );
    printf( "a is " );
    a->print();
    printf( "b is " );
    b->print();
    printf( "length of b is %d/n", b->length() );
    
    return
 0;
}
 
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 怀孕产检医生问的尴尬怎么办 带着节育环做的核磁怎么办 便秘洗肠后最一周未排便怎么办 用了开塞露后肚子疼拉不出来怎么办 冰点脱毛当天用沐浴露洗澡了怎么办 自体脂肪填充脸部但发红又痒怎么办 金矿受伤死亡不给开死亡证明怎么办 手机欠费了导致没信号了怎么办 金立手机指纹硬件无法使用怎么办 试管取卵医生说卵子碎片多怎么办 取卵腹水抽水后尿不通怎么办 手机锁屏密码忘了怎么办求解锁 苹果手表锁屏密码忘记了怎么办 苹果手表锁屏密码忘了怎么办 电脑输密码时点了用户账户怎么办 w7电脑锁屏密码忘记了怎么办 台式电脑w7锁屏密码忘记了怎么办 win7电脑锁屏密码忘记了怎么办 苹果手机4s开机密码忘记了怎么办 苹果4s下载东西忘记密码怎么办 苹果4s不记得开机密码怎么办? 苹果手机id密码忘了怎么办能解锁 苹果5s id密码忘了怎么办? 苹果手机激活锁id忘记了怎么办 苹果刷了机忘了账号无法激活怎么办 三星s7指纹解开锁密码忘了怎么办 索尼手机锁屏密码忘了怎么办 金立手机开机密码忘了怎么办 如果小米手机锁屏密码忘记了怎么办 小米手机锁屏密码忘了怎么办 小米5x忘记了屏保锁屏密码怎么办 htc手机锁屏密码忘了怎么办 苹果7手机解锁密码忘了怎么办 魅族7plus锁屏密码忘了怎么办 捡到苹果手机不知道id密码怎么办 平板不知道id地址和密码怎么办 红米1s刷机变砖了怎么办 车玻璃被鞭炮炸了黑印子怎么办 出轨的事被家人知道后道处传怎么办 村霸霸占土地弱势村民该怎么办? 户户通没有插卡位置信息改变怎么办