一、概述
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
大衛的Design Patterns學習筆記07:Bridge
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章
大衛的Design Patterns學習筆記20:State
一、概述State(狀態)模式用於把一個對象的內部狀態從對象中分離出來,形成單獨的狀態對象,所有與該狀態相關的行爲都放入該狀態對象中。一個對象可能處在
billdavid
2020-06-29 08:29:14
大衛的Design Patterns學習筆記22:Template Method
一、概述Template Method(模板方法)模式定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。Template Method使得子類可以
billdavid
2020-06-29 08:29:13
大衛的Design Patterns學習筆記15:Interpreter
一、概述Interpreter(解釋器)模式描述瞭如何爲簡單的語言定義一個文法,如何在該語言中表示一個句子,以及如何解釋這些句子。在這裏使用語言這個詞
billdavid
2020-06-29 08:29:12
大衛的Design Patterns學習筆記13:Chain of Responsibility
一、概述Chain of Responsibility(職責鏈,以下簡稱CoR)模式通過將多個對象串接成一條鏈(Chain),並沿着這條鏈傳遞上層應用
billdavid
2020-06-29 08:29:11
一文搞懂什麼是面向對象編程思想與設計原則(深度)
谭小先
2020-04-24 21:18:43
大衛的Design Patterns學習筆記12:Proxy
billdavid
2020-02-24 21:30:48
大衛的Design Patterns學習筆記10:Flyweight
billdavid
2020-02-24 21:30:48
大衛的Design Patterns學習筆記05:Singleton
billdavid
2020-02-24 21:30:37
大衛的Design Patterns學習筆記24:後記
billdavid
2020-02-24 21:30:37
大衛的Design Patterns學習筆記14:Command
billdavid
2020-02-24 21:30:37
大衛的Design Patterns學習筆記23:Vistor
billdavid
2020-02-24 21:30:37
大衛的Design Patterns學習筆記17:Mediator
billdavid
2020-02-24 21:30:37
大衛的Design Patterns學習筆記18:Memento
billdavid
2020-02-24 21:30:37
大衛的Design Patterns學習筆記06:Adapter
billdavid
2020-02-24 21:30:37
大衛的Design Patterns學習筆記11:Decorator
billdavid
2020-02-24 21:30:37