第9章 結構型模式—橋接模式
1. 橋接模式(Bridge Pattern)的定義
(1)將抽象部分與它的實現部分分離,使它們都可以獨立地變化
①一般的“抽象”與“實現”是指父子類的繼承關係。但這裡,GoF所謂的“抽象”是如果引起一個類變化是多維度的因素(設為2維),就將其他變化因素抽象成一個介面,在“Abstraction類”中只留這個介面,然後通過物件組合(而不是繼承)的方式去依賴這個介面。而“實現”是指在讓另一個“Implementor類”的子類去實現介面(第2維度的變化)。
② “Abstraction類”和“Implementor類”分別代表了引起類變化的兩個維度(前者是個抽象類、後者是介面),正因為這種分離,所以他們都可以獨立的變化。
③簡單的理解就是,在類中抽離“方法”形成另一個“類”。如動物狗,在設計時,把狗設計成一個類,裡面的“行走”方法從狗中分離出來,形成“行走”介面,並在“狗”類中使用這個“行走”物件。這樣“狗”和“行走”類都可以獨立變化。
(2)橋接模式的結構和說明
①Abstraction:抽象部分的介面。通常在這個物件中,要維護一個實現部分的物件的引用,抽象物件裡面的方法,需要呼叫實現部分的物件來完成。這個物件中的方法,通常都是和具體的業務相關的方法。
②RefinedAbstraction:擴充套件抽象部分的介面。通常在這些物件中,定義跟實際業務相關的方法,這些方法的實現通常會使用Abstraction中定義的方法,也可能需要呼叫實現部分的物件來完成。如上圖中的operation方法中,一般呼叫impl->operationImpl();
③Implementor:定義實現部分的介面。這個介面不用和Abstraction中的方法一致。
④ConcreteImplementor:真正實現Implementor介面的物件。
(3)不使用橋接模式的案例
①蠟筆和毛筆:這兩者的關鍵區別在於筆和顏色是否能夠分離。
②跨平臺的影象瀏覽系統
A、使用多層繼承結構,導致系統中類的個數急劇增加(共17個)。
B、系統擴充套件麻煩,由於每個具體類(*Image)既包含影象檔案格式資訊,又包含作業系統資訊。因此無論是增加新的影象檔案格式還是增加新的作業系統,都需要增加大量的具體類。(如增加TIF,則需要增加3個具體類以便在3種不同的作業系統中顯示。如果增加一個新的作業系統,則需要在每個*Image下增加具體的類。
C、從圖中可以看出,該系統存在兩個獨立變化的維度:影象檔案格式和作業系統。
(4)思考Bridge模式
①Bridge模式的本質:分離抽象和實現。它是解決多繼承的一套方案,把使用繼承改成使用物件組合,解耦了抽象和實現之間固有的繫結關係,從而使“抽象”和“實現”可以沿著各自的緯度獨立的變化。
②Bridge模式的動機:Bridge模式就是為了處理類的多維度變化,其目的是解耦。
③由於抽象部分和實現部分是完全分離的。所以可以在執行時動態組合具體的真實實現,從而動態變換功能。此外,同一個真實實現可以被不同抽象物件使用;反過來,同一個抽象也不能有多個不同的實現。
④橋接模式是一種很實用的結構型設計模式,如果某個類存在多個獨立變化的維度,通過該模式可以將這多個維度分離出來,使它們可以獨立擴充套件。讓系統更符合“單一職責原則”。
【程式設計實驗】傳送提示訊息
//結構型模式:橋接模式
//場景:傳送提示訊息
//訊息:普通訊息、加急訊息和特急訊息
//傳送方式:站內訊息、手機簡訊、E-mail
#include <iostream>
#include <string>
using namespace std;
//實現傳送訊息的統一介面
class MessageImplementor
{
public:
//傳送訊息:
//@param message 要傳送的訊息內容
//@param toUser 訊息傳送的目的人員
virtual void send(string message,string toUser) = 0;
};
//抽象的訊息物件
class AbstractMessage
{
private:
//持有一個實現部分的物件
MessageImplementor& impl;
public:
//建構函式,傳入實現部分的物件
AbstractMessage(MessageImplementor& mi):impl(mi){}
//傳送訊息,轉調實現部分的方法
virtual void sendMessage(string message,string toUser)
{
impl.send(message, toUser);
}
};
//************************具體的訊息傳送方式*******************
//站內訊息的實現
class MessageSMS : public MessageImplementor
{
public:
void send(string message, string toUser)
{
cout <<"SMS:\""<< message << "\" to:" << toUser << endl;
}
};
//E-mail方式的傳送訊息
class MessageEmail : public MessageImplementor
{
public:
void send(string message, string toUser)
{
cout <<"Email:\""<< message << "\" to:" << toUser << endl;
}
};
//Mobile方式的傳送訊息
class MessageMobile : public MessageImplementor
{
public:
void send(string message, string toUser)
{
cout <<"Mobile:\""<< message << "\" to:" << toUser << endl;
}
};
//*********************具體的訊息型別************************
//普通訊息
class CommonMessage : public AbstractMessage
{
public:
CommonMessage(MessageImplementor& im):AbstractMessage(im){}
};
//加急訊息
class UrgencyMessage : public AbstractMessage
{
public:
UrgencyMessage(MessageImplementor& im):AbstractMessage(im){}
void sendMessage(string message,string toUser)
{
message ="Urgency:" + message;
AbstractMessage::sendMessage(message, toUser);
}
};
//特急訊息
class SpecialUrgencyMessage : public AbstractMessage
{
public:
SpecialUrgencyMessage(MessageImplementor& im):AbstractMessage(im){}
void sendMessage(string message,string toUser)
{
message ="SpecialUrgency:" + message;
AbstractMessage::sendMessage(message, toUser);
//還需要加一條待催促的資訊
hurry(100);
}
void hurry(int messageId)
{
//執行摧促的業務,發出催足的資訊,這裡簡單示意一下
cout << "hurry: messageId=" << messageId << endl;
}
};
int main()
{
//1、使用站內短息傳送訊息(普通、加急、特急等)
//建立具體的實現物件
MessageImplementor* impl = new MessageSMS();//選擇站內短息的方式來發送
//建立一個普通訊息物件
AbstractMessage* m = new CommonMessage(*impl); //傳送普通訊息
m->sendMessage("Cup of Tee please","SantaClaus");
delete m;
//建立一個加急訊息物件
m = new UrgencyMessage(*impl); //傳送加急訊息
m->sendMessage("Cup of Tee please","SantaClaus");
delete m;
//建立一個特急訊息物件
m = new SpecialUrgencyMessage(*impl); //傳送特急訊息
m->sendMessage("Cup of Tee please","SantaClaus");
delete m;
delete impl;
cout <<endl;
//2、把實現切換成手機簡訊,然後再實現一遍
//建立具體的實現物件
impl = new MessageMobile();
//建立一個普通訊息物件
m = new CommonMessage(*impl);
m->sendMessage("Cup of Tee please","SantaClaus");
delete m;
//建立一個加急訊息物件
m = new UrgencyMessage(*impl);
m->sendMessage("Cup of Tee please","SantaClaus");
delete m;
//建立一個特急訊息物件
m = new SpecialUrgencyMessage(*impl);
m->sendMessage("Cup of Tee please","SantaClaus");
delete m;
return 0;
}
2. 誰來橋接:誰也建立Implementor物件?
(1)方式1:由客戶端建立Implementor物件,並設定到抽象部分的物件中去。
(2)方式2:可以在抽象部分物件構建的時候,由抽象部分的物件自己來建立。當然也可以給這個抽象部分的物件傳遞一些引數,讓它可以根據引數來建立具體的Implementor物件。
(3)方式3:可以在Abstraction中選擇並建立一個預設的Implementor物件,然後子類可以根據需要改變這個實現。
(4)方式4:可以使用抽象工廠或簡單工廠來選擇並建立具體的Implementor物件。抽象部分的類可以通過呼叫工廠的方法來獲取Implementor物件。
3. 橋接模式的優點
(1)分離抽象和實現部分。讓抽象和實現部分可以獨立變化。對於系統的高層部分,只需要知道抽象部分和實現部分的介面就可以,將依賴實現改為依賴介面程式設計。
(2)更好的擴充套件性:抽象和實現部分可以別分獨立的擴充套件,而不會互相影響
(3)可以動態切換實現:因為一個實現不再是固定繫結到一個抽象介面上,所以可以實現在執行期間動態地切換。
(4)可減少子類的個數:如果採用繼承的實現方式,大約需要兩個緯度上可變化數量的乘積的子類個數;而採用橋接模式只需要兩個緯度可變化數量的子類的和個子類。
4. 橋接模式的使用場景
(1)需要跨越多個平臺的圖形和視窗中系統上。
(2)一個類存在兩個獨立變化維度,且兩個維度都需要擴充套件。
(3)三層架構中通過橋接模式將業務邏輯層(BLL)與資料操作層(DAL)解耦。
5. 三層架構及層間的解耦
(1)三層劃分:使用者介面層(UI)、業務邏輯層(BLL)和資料訪問層(DAL)
(2)各層的作用
①使用者介面層:只負責顯示和採集使用者操作。
②業務邏輯層:負責UI和DAL層之間的資料交換,是系統架構中體現核心價值的部分。它關注點主要集中在業務規則的制定、業務流程的實現和業務需求的有關係統設計。它與系統所對應的領域(Domain)有關。也可以做一些如使用者許可權合法性和資料據式是否正確等驗證檢查。
③資料訪問層:與資料庫直接打交道,如資料庫進行增、刪、改、查等操作。常用的技術如ADO+SQL語句。這裡可能還會有DBUtility類(負責建立資料庫連線)、與工廠方法模式相關的DALFactory抽象類(用於建立如OracleDAL、SQLServerDAL物件之類的工廠)、DataAccess產品類(真正操作資料庫的類)
(3)各層的互動
①UI和BLL:UI層引用BLL和實體類(Entity)。
②BLL和DAL:橋接模式,在BLL中持有一個DAL介面的引用。
③DAL與資料庫:工廠模式或抽象工廠模式。DAL與資料庫間的解耦可參考《抽象工廠模式》部分的內容
【程式設計實驗】利用橋接模式實現三層架構中的業務邏輯層(BLL)與資料訪問層(DAL)的解耦。
//結構型模式:橋接模式
//場景:三層架構業務邏輯層(BLL)與資料操作層(DAL)的解耦
#include <iostream>
#include <string>
#include <list>
using namespace std;
//************************Implementor類******************
//三層架構中的資料訪問層(DAL)
class IDataAccess
{
public:
virtual void addRecord(string name) = 0;
virtual void deleteRecord(string name) = 0;
virtual void updateRecord(string name) = 0;
virtual string getRecord(int index) = 0;
virtual void queryAllRecords() = 0;
};
//具體的DAL層
class CustomerDataAccess: public IDataAccess
{
private:
list<string> customers;
public:
CustomerDataAccess()
{
//實際工程中從資料庫中讀取資料再填充列表
customers.push_back("learning Hard");
customers.push_back("zhang san");
customers.push_back("Li si");
customers.push_back("Wang wu");
}
void addRecord(string name)
{
customers.push_back(name);
}
void deleteRecord(string name)
{
customers.remove(name);
}
void updateRecord(string name)
{
//演示目的
customers.front() = name;
}
string getRecord(int index)
{
list<string>::iterator iter = customers.begin();
advance(iter,index);//取出索引號為index的元素
return *iter;
}
void queryAllRecords()
{
for(list<string>::iterator iter=customers.begin();iter != customers.end();++iter)
{
cout << *iter << endl;
}
}
};
//BLL層的介面
class AbstractBusiness
{
private:
IDataAccess* dataAccess;
public:
AbstractBusiness(IDataAccess* dataAccess)
{
this->dataAccess = dataAccess;
}
virtual void add(string name)
{
dataAccess->addRecord(name);
}
virtual void del(string name)
{
dataAccess->deleteRecord(name);
}
virtual void update(string name)
{
dataAccess->updateRecord(name);
}
virtual string get(int index)
{
return dataAccess->getRecord(index);
}
virtual void showAll()
{
dataAccess->queryAllRecords();
}
};
//具體的業務邏輯類
class CustomersBusiness: public AbstractBusiness
{
public:
CustomersBusiness(IDataAccess* dataAccess):AbstractBusiness(dataAccess){}
void showAll()
{
cout <<"-----------------------"<<endl;
AbstractBusiness::showAll();
cout <<"-----------------------"<<endl;
}
};
int main()
{
IDataAccess* dataAccess = new CustomerDataAccess();
AbstractBusiness* customers = new CustomersBusiness(dataAccess);
customers->add("SantaClaus");
cout<<"add a member:"<<endl;
customers->showAll();
cout <<"delete a member:" << endl;
customers->del("Wang wu");
customers->showAll();
cout << "update a member:" << endl;
customers->update("Learning Hard");
customers->showAll();
return 0;
}