大衛的Design Patterns學習筆記07:Bridge

一、概述
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;
}


參考:
1
、http://home.earthlink.net/~huston2/dp/BridgeDemosCpp
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章