概述
在軟體系統中,某些類型由於自身的邏輯,它具有兩個或多個維度的變化,那么如何應對這種“多維度的變化”?如何利用面向對象的技術來使得該類型能夠輕鬆的沿著多個方向進行變化,而又不引入額外的複雜度?這就要使用Bridge模式。
意圖
【GOF95】在提出橋樑模式的時候指出,橋樑模式的用意是"將抽象化(Abstraction)與實現化(Implementation)脫耦,使得二者可以獨立地變化"。這句話有三個關鍵字,也就是抽象化、實現化和脫耦。
抽象化
存在於多個實體中的共同的概念性聯繫,就是抽象化。作為一個過程,抽象化就是忽略一些信息,從而把不同的實體當做同樣的實體對待【LISKOV94】。
實現化
抽象化給出的具體實現,就是實現化。
脫耦
所謂耦合,就是兩個實體的行為的某種強關聯。而將它們的強關聯去掉,就是耦合的解脫,或稱脫耦。在這裡,脫耦是指將抽象化和實現化之間的耦合解脫開,或者說是將它們之間的強關聯改換成弱關聯。
將兩個角色之間的繼承關係改為聚合關係,就是將它們之間的強關聯改換成為弱關聯。因此,橋樑模式中的所謂脫耦,就是指在一個軟體系統的抽象化和實現化之間使用組合/聚合關係而不是繼承關係,從而使兩者可以相對獨立地變化。這就是橋樑模式的用意。
結構
可以看出,這個系統含有兩個等級結構,也就是:
由抽象化角色和修正抽象化角色組成的抽象化等級結構。
由實現化角色和兩個具體實現化角色所組成的實現化等級結構。
橋樑模式所涉及的角色有:
抽象化(Abstraction)角色:抽象化給出的定義,並保存一個對實現化對象的引用。
修正抽象化(Refined Abstraction)角色:擴展抽象化角色,改變和修正父類對抽象化的定義。
實現化(Implementor)角色:這個角色給出實現化角色的接口,但不給出具體的實現。必須指出的是,這個接口不一定和抽象化角色的接口定義相同,實際上,這兩個接口可以非常不一樣。實現化角色應當只給出底層操作,而抽象化角色應當只給出基於底層操作的更高一層的操作。
具體實現化(Concrete Implementor)角色:這個角色給出實現化角色接口的具體實現。
示意性原始碼
// 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();
}
}
C++橋接模式
由遇到的問題引出橋接模式
總結面向對象實際上就兩句話:一是松耦合(Coupling),二是高內聚(Cohesion)。面向對象系統追求的目標就是儘可能地提高系統模組內部的內聚(Cohesion)、儘可能降低模組間的耦合(Coupling)。然而這也是面向對象設計過程中最為難把握的部分,大家肯定在OO 系統的開發過程中遇到這樣的問題:
客戶給了你一個需求,於是使用一個類來實現(A);
客戶需求變化,有兩個算法實現功能,於是改變設計,我們通過一個抽象的基類,再定義兩個具體類實現兩個不同的算法(A1 和 A2);
客戶又告訴我們說對於不同的作業系統,於是再抽象一個層次,作為一個抽象基類A0,在分別為每個作業系統派生具體類(A00 和 A01,其中 A00 表示原來的類 A)實現不同作業系統上的客戶需求,這樣我們就有了一共 4 個類。
可能用戶的需求又有變化,比如說又有了一種新的算法……..
我們陷入了一個需求變化的鬱悶當中,也因此帶來了類的迅速膨脹。
橋接模式則正是解決了這類問題。
模式選擇
橋接模式典型的結構圖為:
圖 2-1:橋接模式結構圖
在橋接模式的結構圖中可以看到,系統被分為兩個相對獨立的部分,左邊是抽象部分,右邊是實現部分,這兩個部分可以互相獨立地進行修改:例如上面問題中的客戶需求變化,當用戶需求需要從 Abstraction 派生一個具體子類時候,並不需要像上面通過繼承方式實現時候需要添加子類 A1 和 A2 了。另外當上面問題中由於算法添加也只用改變右邊實現(添加一個具體化子類),而右邊不用在變化,也不用添加具體子類了。
一切都變得 elegant!
橋接模式的實現
完整代碼示例(code):橋接模式的實現起來並不是特別困難,這裡為了方便初學者的學習和參考,將給出完整的實現代碼(所有代碼採用 C++實現,並在 VC 6.0 下測試運行)。
代碼片斷 1:Abstraction.h
//Abstraction.h
#ifndef _ABSTRACTION_H_
#define _ABSTRACTION_H_
class AbstractionImp;
class Abstraction{
public:
virtual ~Abstraction();
virtual void Operation() = 0;
protected:
Abstraction();
private:
};
class RefinedAbstraction:public Abstraction{
public:
RefinedAbstraction(AbstractionImp* imp);
~RefinedAbstraction();
void Operation();
protected:
private:
AbstractionImp* _imp;
};
#endif //~_ABSTRACTION_H_
代碼片斷 2:Abstraction.cpp
//Abstraction.cpp
#include "Abstraction.h"
#include "AbstractionImp.h"
#include
using namespace std;
Abstraction::Abstraction(){
}
Abstraction::~Abstraction(){
}
RefinedAbstraction::RefinedAbstraction(AbstractionImp* imp){
_imp = imp;
}
RefinedAbstraction::~RefinedAbstraction(){
}
void RefinedAbstraction::Operation(){
_imp->Operation();
}
代碼片斷 3:AbstractionImp.h
//AbstractionImp.h
#ifndef _ABSTRACTIONIMP_H_
#define _ABSTRACTIONIMP_H_
class AbstractionImp{
public:
virtual ~AbstractionImp();
virtual void Operation() = 0;
protected:
AbstractionImp();
private:
};
class ConcreteAbstractionImpA:public AbstractionImp{
public:
ConcreteAbstractionImpA();
~ConcreteAbstractionImpA();
virtual void Operation();
protected:
private:
};
class ConcreteAbstractionImpB:public AbstractionImp{
public:
ConcreteAbstractionImpB();
~ConcreteAbstractionImpB();
virtual void Operation();
protected:
private:
};
#endif //~_ABSTRACTIONIMP_H_
代碼片斷 4:AbstractionImp.cpp
//AbstractionImp.cpp
#include "AbstractionImp.h"
#include
using namespace std;
AbstractionImp::AbstractionImp(){
}
AbstractionImp::~AbstractionImp(){
}
void AbstractionImp::Operation(){
cout<<"AbstractionImp....imp..."<
}
ConcreteAbstractionImpA::ConcreteAbstractionImpA(){
}
ConcreteAbstractionImpA::~ConcreteAbstractionImpA(){
}
void ConcreteAbstractionImpA::Operation(){
cout<<"ConcreteAbstractionImpA...."<
}
ConcreteAbstractionImpB::ConcreteAbstractionImpB(){
}
ConcreteAbstractionImpB::~ConcreteAbstractionImpB(){
}
void ConcreteAbstractionImpB::Operation(){
cout<<"ConcreteAbstractionImpB...."<
}
代碼片斷 5:main.cpp
//main.cpp
#include "Abstraction.h"
#include "AbstractionImp.h"
#include
using namespace std;
int main(int argc,char* argv[]){
AbstractionImp* imp = new ConcreteAbstractionImpA();
Abstraction* abs = new RefinedAbstraction(imp);
abs->Operation();
return 0;
}
代碼說明:橋接模式將抽象和實現分別獨立實現,在代碼中就是 Abstraction 類和 AbstractionImp類。
關於橋接模式的討論
橋接是設計模式中比較複雜和難理解的模式之一,也是 OO 開發與設計中經常會用到的模式之一。使用組合(委託)的方式將抽象和實現徹底地解耦,這樣的好處是抽象和實現可以分別獨立地變化,系統的耦合性也得到了很好的降低。GoF 在說明橋接模式時,在意圖中指出橋接模式"將抽象部分與它的實現部分分離,使得它們可以獨立地變化"。這句話很簡單,但是也很複雜,連 Bruce Eckel 在他的大作《Thinking in Patterns》中說"橋接模式是 GoF 所講述得最不好(Poorly-described)的模式",個人覺得也正是如此。原因就在於 GoF 的那句話中的"實現"該怎么去理解:"實現"特別是和"抽象"放在一起的時候我們"默認"的理解是"實現"就是"抽象"的具體子類的實現,但是這裡 GoF 所謂的"實現"的含義不是指抽象基類的具體子類對抽象基類中虛函式(接口)的實現,是和繼承結合在一起的。而這裡的"實現"的含義指的是怎么去實現用戶的需求,並且指的是通過組合(委託)的方式實現的,因此這裡的實現不是指的繼承基類、實現基類接口,而是指的是通過對象組合實現用戶的需求。理解了這一點也就理解了橋接模式,理解了橋接模式,你的設計就會更加 Elegant 了。
實際上上面使用橋接模式和使用帶來問題方式的解決方案的根本區別在於是通過繼承還是通過組合的方式去實現一個功能需求。因此面向對象分析和設計中有一個原則就是:Favor Composition Over Inheritance。其原因也正在這裡。