一、 橋梁(Bridge)模式
橋梁模式是一個(gè)非常有用的模式,也是比較復(fù)雜的一個(gè)模式。熟悉這個(gè)模式對(duì)于理解面向?qū)ο蟮脑O(shè)計(jì)原則,包括"開(kāi)-閉"原則(OCP)以及組合/聚合復(fù)用原則(CARP)都很有幫助。理解好這兩個(gè)原則,有助于形成正確的設(shè)計(jì)思想和培養(yǎng)良好的設(shè)計(jì)風(fēng)格。
橋梁模式的用意
【GOF95】在提出橋梁模式的時(shí)候指出,橋梁模式的用意是"將抽象化(Abstraction)與實(shí)現(xiàn)化(Implementation)脫耦,使得二者可以獨(dú)立地變化"。這句話有三個(gè)關(guān)鍵詞,也就是抽象化、實(shí)現(xiàn)化和脫耦。
抽象化
存在于多個(gè)實(shí)體中的共同的概念性聯(lián)系,就是抽象化。作為一個(gè)過(guò)程,抽象化就是忽略一些信息,從而把不同的實(shí)體當(dāng)做同樣的實(shí)體對(duì)待【LISKOV94】。
實(shí)現(xiàn)化
抽象化給出的具體實(shí)現(xiàn),就是實(shí)現(xiàn)化。
脫耦
所謂耦合,就是兩個(gè)實(shí)體的行為的某種強(qiáng)關(guān)聯(lián)。而將它們的強(qiáng)關(guān)聯(lián)去掉,就是耦合的解脫,或稱(chēng)脫耦。在這里,脫耦是指將抽象化和實(shí)現(xiàn)化之間的耦合解脫開(kāi),或者說(shuō)是將它們之間的強(qiáng)關(guān)聯(lián)改換成弱關(guān)聯(lián)。
將兩個(gè)角色之間的繼承關(guān)系改為聚合關(guān)系,就是將它們之間的強(qiáng)關(guān)聯(lián)改換成為弱關(guān)聯(lián)。因此,橋梁模式中的所謂脫耦,就是指在一個(gè)軟件系統(tǒng)的抽象化和實(shí)現(xiàn)化之間使用組合/聚合關(guān)系而不是繼承關(guān)系,從而使兩者可以相對(duì)獨(dú)立地變化。這就是橋梁模式的用意。
二、 橋梁模式的結(jié)構(gòu)
橋梁模式【GOF95】是對(duì)象的結(jié)構(gòu)模式,又稱(chēng)為柄體(Handle and Body)模式或接口(Interface)模式。
下圖所示就是一個(gè)實(shí)現(xiàn)了橋梁模式的示意性系統(tǒng)的結(jié)構(gòu)圖。

可以看出,這個(gè)系統(tǒng)含有兩個(gè)等級(jí)結(jié)構(gòu),也就是:
- 由抽象化角色和修正抽象化角色組成的抽象化等級(jí)結(jié)構(gòu)。
- 由實(shí)現(xiàn)化角色和兩個(gè)具體實(shí)現(xiàn)化角色所組成的實(shí)現(xiàn)化等級(jí)結(jié)構(gòu)。
橋梁模式所涉及的角色有:
- 抽象化(Abstraction)角色:抽象化給出的定義,并保存一個(gè)對(duì)實(shí)現(xiàn)化對(duì)象的引用。
- 修正抽象化(Refined Abstraction)角色:擴(kuò)展抽象化角色,改變和修正父類(lèi)對(duì)抽象化的定義。
- 實(shí)現(xiàn)化(Implementor)角色:這個(gè)角色給出實(shí)現(xiàn)化角色的接口,但不給出具體的實(shí)現(xiàn)。必須指出的是,這個(gè)接口不一定和抽象化角色的接口定義相同,實(shí)際上,這兩個(gè)接口可以非常不一樣。實(shí)現(xiàn)化角色應(yīng)當(dāng)只給出底層操作,而抽象化角色應(yīng)當(dāng)只給出基于底層操作的更高一層的操作。
- 具體實(shí)現(xiàn)化(Concrete Implementor)角色:這個(gè)角色給出實(shí)現(xiàn)化角色接口的具體實(shí)現(xiàn)。
三、 橋梁模式的示意性源代碼
// Bridge pattern -- Structural example
using System;

// "Abstraction"
class Abstraction
  {
// Fields
protected Implementor implementor;

// Properties
public Implementor Implementor
 {
 set { implementor = value; }
}

// Methods
virtual public void Operation()
 {
implementor.Operation();
}
}

// "Implementor"
abstract class Implementor
  {
// Methods
abstract public void Operation();
}

// "RefinedAbstraction"
class RefinedAbstraction : Abstraction
  {
// Methods
override public void Operation()
 {
implementor.Operation();
}
}

// "ConcreteImplementorA"
class ConcreteImplementorA : Implementor
  {
// Methods
override public void Operation()
 {
Console.WriteLine("ConcreteImplementorA Operation");
}
}

// "ConcreteImplementorB"
class ConcreteImplementorB : Implementor
  {
// Methods
override public void Operation()
 {
Console.WriteLine("ConcreteImplementorB Operation");
}
}

 /**////
/// Client test
/// public class Client
  {
public static void Main( string[] args )
 {
Abstraction abstraction = new RefinedAbstraction();

// Set implementation and call
abstraction.Implementor = new ConcreteImplementorA();
abstraction.Operation();

// Change implemention and call
abstraction.Implementor = new ConcreteImplementorB();
abstraction.Operation();
}
}
四、 調(diào)制解調(diào)器問(wèn)題
感覺(jué)《敏捷軟件開(kāi)發(fā)-原則、模式與實(shí)踐》中關(guān)于Bridge模式的例子很好。(《Java與模式》一書(shū)33章的對(duì)變化的封裝一節(jié)也寫(xiě)得很不錯(cuò),推薦
大家讀一讀。它深入的闡述了《Design Patterns Explained》一書(shū)中"1)Design to interfaces.
2)Favor composition over inheritance. 3)Find what varies and
encapsulate it"的三個(gè)觀點(diǎn)。)。

如圖所示,有大量的調(diào)制解調(diào)器客戶(hù)程序在使用Modem接口。Modem接口被幾個(gè)派生類(lèi)HayesModem、USRoboticsModem和
EarniesModem實(shí)現(xiàn)。它很好地遵循了OCP、LSP和DIP。當(dāng)增加新種類(lèi)的調(diào)制解調(diào)器時(shí),調(diào)制解調(diào)器的客戶(hù)程序不會(huì)受影響。
假定這種情形持續(xù)了幾年,并有許多調(diào)制解調(diào)器的客戶(hù)程序都在使用著Modem接口?,F(xiàn)出現(xiàn)了一種不撥號(hào)的調(diào)制解調(diào)器,被稱(chēng)為專(zhuān)用調(diào)制解調(diào)器。它們位
于一條專(zhuān)用連接的兩端。有幾個(gè)新應(yīng)用程序使用這些專(zhuān)用調(diào)制解調(diào)器,它們無(wú)需撥號(hào)。我們稱(chēng)這些使用者為DedUser。但是,客戶(hù)希望當(dāng)前所有的調(diào)制解調(diào)器
客戶(hù)程序都可以使用這些專(zhuān)用調(diào)制解調(diào)器。他們不希望去更改許許多多的調(diào)制解調(diào)器客戶(hù)應(yīng)用程序,所以完全可以讓這些調(diào)制解調(diào)器客戶(hù)程序去撥一些假
(dummy)電話號(hào)碼。
如果能選擇的話,我們會(huì)把系統(tǒng)的設(shè)計(jì)更改為下圖所示的那樣。

我們把撥號(hào)和通信功能分離為兩個(gè)不同的接口。原來(lái)的調(diào)制解調(diào)器實(shí)現(xiàn)這兩個(gè)接口,而調(diào)制解調(diào)器客戶(hù)程序使用這兩個(gè)接口。DedUser只使用
Modem接口,而DedicateModem只實(shí)現(xiàn)Modem接口。但這樣做會(huì)要求我們更改所有的調(diào)制解調(diào)器客戶(hù)程序--這是客戶(hù)不允許的。
一個(gè)可能的解決方案是讓DedicatedModem從Modem派生并且把dial方法和hangup方法實(shí)現(xiàn)為空,就像下面這樣:

幾個(gè)月后,已經(jīng)有了大量的DedUser,此時(shí)客戶(hù)提出了一個(gè)新的更改。為了能撥?chē)?guó)際電話號(hào)碼、信用卡電話、PIN標(biāo)識(shí)電話等等,必修對(duì)現(xiàn)有dial中使用char[10]存儲(chǔ)號(hào)碼改為能夠撥打任意長(zhǎng)度的電話號(hào)碼。
顯然,所有的調(diào)制解調(diào)器客戶(hù)程序都必須更改。客戶(hù)同意了對(duì)調(diào)制解調(diào)器客戶(hù)程序的更改,因?yàn)樗麄儎e無(wú)選擇。糟糕的是,現(xiàn)在必須要去告訴DedUser的編寫(xiě)者,他們必須要更改他們的代碼!你可以想象他們聽(tīng)到這個(gè)會(huì)有多高興。本來(lái)他們是不用調(diào)用dial的。
這就是許多項(xiàng)目都會(huì)具有的那種有害的混亂依賴(lài)關(guān)系。系統(tǒng)某一部分中的一個(gè)雜湊體(kludge)創(chuàng)建了一個(gè)有害的依賴(lài)關(guān)系,最終導(dǎo)致系統(tǒng)中完全無(wú)關(guān)的部分出現(xiàn)問(wèn)題。
如果使用ADAPTER模式解決最初的問(wèn)題的話,就可以避免這個(gè)嚴(yán)重問(wèn)題。如圖:

請(qǐng)注意,雜湊體仍然存在。適配器仍然要模擬連接狀態(tài)。然而,所有的依賴(lài)關(guān)系都是從適配器發(fā)起的。雜湊體和系統(tǒng)隔離,藏身于幾乎無(wú)人知曉的適配器中。
BRIDGE模式
看待這個(gè)問(wèn)題,還有另外一個(gè)方式。現(xiàn)在,出現(xiàn)了另外一種切分Modem層次結(jié)構(gòu)的方式。如下圖:

這不是一個(gè)理想的結(jié)構(gòu)。每當(dāng)增加一款新硬件時(shí),就必須創(chuàng)建兩個(gè)新類(lèi)--一個(gè)針對(duì)專(zhuān)用的情況,一個(gè)針對(duì)撥號(hào)的情況。每當(dāng)增加一種新連接類(lèi)型時(shí),就必須創(chuàng)建3個(gè)新類(lèi),分別對(duì)應(yīng)3款不同的硬件。如果這兩個(gè)自由度根本就是不穩(wěn)定的,那么不用多久,就會(huì)出現(xiàn)大量的派生類(lèi)。
在類(lèi)型層次結(jié)構(gòu)具有多個(gè)自由度的情況中,BRIDGE模式通常是有用的。我們可以把這些層次結(jié)構(gòu)分開(kāi)并通過(guò)橋把它們結(jié)合到一起,而不是把它們合并起來(lái)。如圖:

我們把調(diào)制解調(diào)器類(lèi)層次結(jié)構(gòu)分成兩個(gè)層次結(jié)構(gòu)。一個(gè)表示連接方法,另一個(gè)表示硬件。
這個(gè)結(jié)構(gòu)雖然復(fù)雜,但是很有趣。它的創(chuàng)建不會(huì)影響到調(diào)制解調(diào)器的使用者,并且還完全分離了連接策略和硬件實(shí)現(xiàn)。
ModemConnectController的每個(gè)派生類(lèi)代表了一個(gè)新的連接策略。在這個(gè)策略的實(shí)現(xiàn)中可以使用sendlmp、receivelmp、
diallmp和hanglmp。新imp方法的增加不會(huì)影響到使用者。可以使用ISP來(lái)給連接控制類(lèi)增加新的接口。這種做法可以創(chuàng)建出一條遷移路徑,調(diào)
制解調(diào)器的客戶(hù)程序可以沿著這條路徑慢慢地得到一個(gè)比dial和hangup層次更高的API。
五、 另外一個(gè)實(shí)際應(yīng)用Bridge模式的例子
該例子演示了業(yè)務(wù)對(duì)象(BusinessObject)通過(guò)Bridge模式與數(shù)據(jù)對(duì)象(DataObject)解耦。數(shù)據(jù)對(duì)象的實(shí)現(xiàn)可以在不改變客戶(hù)端代碼的情況下動(dòng)態(tài)進(jìn)行更換。
// Bridge pattern -- Real World example
using System;
using System.Collections;

// "Abstraction"
class BusinessObject
  {
// Fields
private DataObject dataObject;
protected string group;

// Constructors
public BusinessObject( string group )
 {
this.group = group;
}

// Properties
public DataObject DataObject
 {
 set { dataObject = value; }
 get { return dataObject; }
}

// Methods
virtual public void Next()
 { dataObject.NextRecord(); }

virtual public void Prior()
 { dataObject.PriorRecord(); }

virtual public void New( string name )
 { dataObject.NewRecord( name ); }

virtual public void Delete( string name )
 { dataObject.DeleteRecord( name ); }

virtual public void Show()
 { dataObject.ShowRecord(); }

virtual public void ShowAll()
 {
Console.WriteLine( "Customer Group: {0}", group );
dataObject.ShowAllRecords();
}
}

// "RefinedAbstraction"
class CustomersBusinessObject : BusinessObject
  {
// Constructors
public CustomersBusinessObject( string group )
 : base( group ) {}

// Methods
override public void ShowAll()
 {
// Add separator lines
Console.WriteLine();
Console.WriteLine( "------------------------" );
base.ShowAll();
Console.WriteLine( "------------------------" );
}
}

// "Implementor"
abstract class DataObject
  {
// Methods
abstract public void NextRecord();
abstract public void PriorRecord();
abstract public void NewRecord( string name );
abstract public void DeleteRecord( string name );
abstract public void ShowRecord();
abstract public void ShowAllRecords();
}

// "ConcreteImplementor"
class CustomersDataObject : DataObject
  {
// Fields
private ArrayList customers = new ArrayList();
private int current = 0;

// Constructors
public CustomersDataObject()
 {
// Loaded from a database
customers.Add( "Jim Jones" );
customers.Add( "Samual Jackson" );
customers.Add( "Allen Good" );
customers.Add( "Ann Stills" );
customers.Add( "Lisa Giolani" );
}

// Methods
public override void NextRecord()
 {
if( current <= customers.Count - 1 )
current++;
}

public override void PriorRecord()
 {
if( current > 0 )
current--;
}

public override void NewRecord( string name )
 {
customers.Add( name );
}

public override void DeleteRecord( string name )
 {
customers.Remove( name );
}

public override void ShowRecord()
 {
Console.WriteLine( customers[ current ] );
}

public override void ShowAllRecords()
 {
foreach( string name in customers )
Console.WriteLine( " " + name );
}
}

 /**////
/// Client test
/// public class BusinessApp
  {
public static void Main( string[] args )
 {
// Create RefinedAbstraction
CustomersBusinessObject customers =
new CustomersBusinessObject(" Chicago ");

// Set ConcreteImplementor
customers.DataObject = new CustomersDataObject();

// Exercise the bridge
customers.Show();
customers.Next();
customers.Show();
customers.Next();
customers.Show();
customers.New( "Henry Velasquez" );

customers.ShowAll();
}
}
六、 在什么情況下應(yīng)當(dāng)使用橋梁模式
根據(jù)上面的分析,在以下的情況下應(yīng)當(dāng)使用橋梁模式:
- 如果一個(gè)系統(tǒng)需要在構(gòu)件的抽象化角色和具體化角色之間增加更多的靈活性,避免在兩個(gè)層次之間建立靜態(tài)的聯(lián)系。
- 設(shè)計(jì)要求實(shí)現(xiàn)化角色的任何改變不應(yīng)當(dāng)影響客戶(hù)端,或者說(shuō)實(shí)現(xiàn)化角色的改變對(duì)客戶(hù)端是完全透明的。
- 一個(gè)構(gòu)件有多于一個(gè)的抽象化角色和實(shí)現(xiàn)化角色,系統(tǒng)需要它們之間進(jìn)行動(dòng)態(tài)耦合。
- 雖然在系統(tǒng)中使用繼承是沒(méi)有問(wèn)題的,但是由于抽象化角色和具體化角色需要獨(dú)立變化,設(shè)計(jì)要求需要獨(dú)立管理這兩者。
|