設計模式8-Bridge 橋模式
- 由來與目的
- 模式定義
- 結構
- 代碼推導
- 1. 類和接口的定義
- 2. 平臺實現
- 3. 業務抽象
- 4. 使用示例
- 總結
- 1. 類數量過多,復雜度高
- 2. 代碼重復
- 3. 不符合單一職責原則
- 4. 缺乏擴展性
- 改進后的設計
- 1. 抽象和實現分離(橋接模式)
- 2. 抽象類
- 3. 使用裝飾者模式添加功能
- 4. 使用示例
- 改進后的優點
- 疑問
- 1. `protected` 訪問級別的意義
- 2. `protected` 的使用場景
- 具體分析代碼中的選擇
- 1. `protected` 訪問級別的好處
- 2. 如果使用`private`
- 3. 如果使用`public`
- 總結
- 要點總結
橋模式也屬于單一職責模式中的一種。
由來與目的
橋模式的由來以及目的:由于某些類型的固有的實現邏輯使他們具有兩個變化的維度乃至多個維度的變化。那么此時如何應對這種多維度的變化,如何利用面向對象技術?來使得類型可以輕松地沿著兩個乃至多個方向進行變化,而不引入額外的復雜度呢?那么喬模式就應運而生,他的存在就是為了解決此類問題。
模式定義
將抽象部分也就是業務功能,與實現部分分離,使他們都可以獨立的變化。
結構
代碼推導
class Messager{
public:virtual void Login(string username, string password)=0;virtual void SendMessage(string message)=0;virtual void SendPicture(Image image)=0;virtual void PlaySound()=0;virtual void DrawShape()=0;virtual void WriteText()=0;virtual void Connect()=0;virtual ~Messager(){}
};//平臺實現class PCMessagerBase : public Messager{
public:virtual void PlaySound(){//**********}virtual void DrawShape(){//**********}virtual void WriteText(){//**********}virtual void Connect(){//**********}
};class MobileMessagerBase : public Messager{
public:virtual void PlaySound(){//==========}virtual void DrawShape(){//==========}virtual void WriteText(){//==========}virtual void Connect(){//==========}
};//業務抽象class PCMessagerLite : public PCMessagerBase {
public:virtual void Login(string username, string password){PCMessagerBase::Connect();//........}virtual void SendMessage(string message){PCMessagerBase::WriteText();//........}virtual void SendPicture(Image image){PCMessagerBase::DrawShape();//........}
};class PCMessagerPerfect : public PCMessagerBase {
public:virtual void Login(string username, string password){PCMessagerBase::PlaySound();//********PCMessagerBase::Connect();//........}virtual void SendMessage(string message){PCMessagerBase::PlaySound();//********PCMessagerBase::WriteText();//........}virtual void SendPicture(Image image){PCMessagerBase::PlaySound();//********PCMessagerBase::DrawShape();//........}
};class MobileMessagerLite : public MobileMessagerBase {
public:virtual void Login(string username, string password){MobileMessagerBase::Connect();//........}virtual void SendMessage(string message){MobileMessagerBase::WriteText();//........}virtual void SendPicture(Image image){MobileMessagerBase::DrawShape();//........}
};class MobileMessagerPerfect : public MobileMessagerBase {
public:virtual void Login(string username, string password){MobileMessagerBase::PlaySound();//********MobileMessagerBase::Connect();//........}virtual void SendMessage(string message){MobileMessagerBase::PlaySound();//********MobileMessagerBase::WriteText();//........}virtual void SendPicture(Image image){MobileMessagerBase::PlaySound();//********MobileMessagerBase::DrawShape();//........}
};void Process(){//編譯時裝配Messager *m =new MobileMessagerPerfect();
}
這段代碼實現了一個跨平臺的消息傳遞系統,其中包含了多個層次的抽象和具體實現。它通過將平臺相關的功能和業務邏輯分離來實現不同的消息傳遞方式。
1. 類和接口的定義
class Messager {
public:virtual void Login(string username, string password) = 0;virtual void SendMessage(string message) = 0;virtual void SendPicture(Image image) = 0;virtual void PlaySound() = 0;virtual void DrawShape() = 0;virtual void WriteText() = 0;virtual void Connect() = 0;virtual ~Messager() {}
};
Messager
是一個抽象基類,定義了登錄、發送消息、發送圖片以及平臺相關的功能(播放聲音、繪制圖形、寫文本、連接)。
2. 平臺實現
class PCMessagerBase : public Messager {
public:virtual void PlaySound() {//**********}virtual void DrawShape() {//**********}virtual void WriteText() {//**********}virtual void Connect() {//**********}
};class MobileMessagerBase : public Messager {
public:virtual void PlaySound() {//==========}virtual void DrawShape() {//==========}virtual void WriteText() {//==========}virtual void Connect() {//==========}
};
PCMessagerBase
和 MobileMessagerBase
是平臺相關的具體實現類,分別實現了 PC 和移動平臺上的功能。這兩個類分別實現了播放聲音、繪制圖形、寫文本和連接的方法。
3. 業務抽象
class PCMessagerLite : public PCMessagerBase {
public:virtual void Login(string username, string password) {PCMessagerBase::Connect();//........}virtual void SendMessage(string message) {PCMessagerBase::WriteText();//........}virtual void SendPicture(Image image) {PCMessagerBase::DrawShape();//........}
};class PCMessagerPerfect : public PCMessagerBase {
public:virtual void Login(string username, string password) {PCMessagerBase::PlaySound();//********PCMessagerBase::Connect();//........}virtual void SendMessage(string message) {PCMessagerBase::PlaySound();//********PCMessagerBase::WriteText();//........}virtual void SendPicture(Image image) {PCMessagerBase::PlaySound();//********PCMessagerBase::DrawShape();//........}
};class MobileMessagerLite : public MobileMessagerBase {
public:virtual void Login(string username, string password) {MobileMessagerBase::Connect();//........}virtual void SendMessage(string message) {MobileMessagerBase::WriteText();//........}virtual void SendPicture(Image image) {MobileMessagerBase::DrawShape();//........}
};class MobileMessagerPerfect : public MobileMessagerBase {
public:virtual void Login(string username, string password) {MobileMessagerBase::PlaySound();//********MobileMessagerBase::Connect();//........}virtual void SendMessage(string message) {MobileMessagerBase::PlaySound();//********MobileMessagerBase::WriteText();//........}virtual void SendPicture(Image image) {MobileMessagerBase::PlaySound();//********MobileMessagerBase::DrawShape();//........}
};
這些類繼承自平臺實現類,并且在每個方法中調用了相應的基類方法,添加了業務邏輯。具體而言,PCMessagerLite
和 MobileMessagerLite
是簡化版的實現,而 PCMessagerPerfect
和 MobileMessagerPerfect
則是帶有更多功能(如播放聲音)的完整實現。
4. 使用示例
void Process() {//編譯時裝配Messager *m = new MobileMessagerPerfect();
}
Process
函數展示了如何創建一個 MobileMessagerPerfect
實例并賦值給 Messager
指針。
總結
這段代碼通過將不同平臺的實現和業務邏輯分開,展示了如何使用繼承和多態來實現跨平臺的消息傳遞系統。它遵循了開閉原則(對擴展開放,對修改關閉),使得添加新的平臺或功能變得更容易。具體地,Messager
定義了接口,PCMessagerBase
和 MobileMessagerBase
實現了平臺相關的功能,而 PCMessagerLite
、PCMessagerPerfect
、MobileMessagerLite
和 MobileMessagerPerfect
實現了具體的業務邏輯。
這段代碼雖然展示了一個靈活的跨平臺消息傳遞系統的設計,但仍然存在一些缺陷和改進空間。以下是一些主要的缺陷及其解決方案:
1. 類數量過多,復雜度高
缺陷:每添加一個新功能或新平臺,都需要創建大量新的類,導致類數量爆炸,假設此時平臺類數量為n ,平臺實現功能類數量為m ,那么此時類的數量為1+n+m*n,增加了代碼的復雜性和維護成本。
解決方案:可以使用橋接模式(Bridge Pattern),將抽象和實現分離,從而減少類的數量和復雜度。
2. 代碼重復
缺陷:許多方法在不同類中有重復的實現,如 PlaySound()
、DrawShape()
、WriteText()
和 Connect()
方法在不同類中基本相同。
解決方案:將這些方法提取到一個單獨的類中,或者使用組合而不是繼承來減少代碼重復。
3. 不符合單一職責原則
缺陷:Messager
類同時處理平臺相關的實現和業務邏輯,違背了單一職責原則,使得類的職責不清晰。
解決方案:將平臺相關的實現和業務邏輯分離,使用橋接模式或策略模式將這些職責分開。
4. 缺乏擴展性
缺陷:添加新功能(如加密、壓縮等)需要創建大量新的類,缺乏靈活性。
解決方案:可以使用裝飾者模式(Decorator Pattern)來動態地為對象添加職責,而不需要創建大量的子類。
改進后的設計
使用橋接模式和裝飾者模式重新設計:
1. 抽象和實現分離(橋接模式)
class MessagerImp {
public:virtual void PlaySound() = 0;virtual void DrawShape() = 0;virtual void WriteText() = 0;virtual void Connect() = 0;virtual ~MessagerImp() {}
};class PCMessagerImp : public MessagerImp {
public:void PlaySound() override {// PC平臺實現}void DrawShape() override {// PC平臺實現}void WriteText() override {// PC平臺實現}void Connect() override {// PC平臺實現}
};class MobileMessagerImp : public MessagerImp {
public:void PlaySound() override {// 移動平臺實現}void DrawShape() override {// 移動平臺實現}void WriteText() override {// 移動平臺實現}void Connect() override {// 移動平臺實現}
};
2. 抽象類
class Messager {
protected:MessagerImp* imp;
public:Messager(MessagerImp* imp) : imp(imp) {}virtual void Login(string username, string password) = 0;virtual void SendMessage(string message) = 0;virtual void SendPicture(Image image) = 0;virtual ~Messager() {}
};class MessagerLite : public Messager {
public:MessagerLite(MessagerImp* imp) : Messager(imp) {}void Login(string username, string password) override {imp->Connect();// Lite版特有的登錄邏輯}void SendMessage(string message) override {imp->WriteText();// Lite版特有的發送消息邏輯}void SendPicture(Image image) override {imp->DrawShape();// Lite版特有的發送圖片邏輯}
};class MessagerPerfect : public Messager {
public:MessagerPerfect(MessagerImp* imp) : Messager(imp) {}void Login(string username, string password) override {imp->PlaySound();imp->Connect();// Perfect版特有的登錄邏輯}void SendMessage(string message) override {imp->PlaySound();imp->WriteText();// Perfect版特有的發送消息邏輯}void SendPicture(Image image) override {imp->PlaySound();imp->DrawShape();// Perfect版特有的發送圖片邏輯}
};
3. 使用裝飾者模式添加功能
class MessagerDecorator : public Messager {
protected:Messager* messager;
public:MessagerDecorator(Messager* messager) : Messager(messager->imp), messager(messager) {}void Login(string username, string password) override {messager->Login(username, password);}void SendMessage(string message) override {messager->SendMessage(message);}void SendPicture(Image image) override {messager->SendPicture(image);}
};class EncryptedMessager : public MessagerDecorator {
public:EncryptedMessager(Messager* messager) : MessagerDecorator(messager) {}void Login(string username, string password) override {// 加密登錄MessagerDecorator::Login(username, password);}void SendMessage(string message) override {// 加密消息MessagerDecorator::SendMessage(message);}void SendPicture(Image image) override {// 加密圖片MessagerDecorator::SendPicture(image);}
};
4. 使用示例
void Process() {MessagerImp* pcImp = new PCMessagerImp();Messager* m = new EncryptedMessager(new MessagerPerfect(pcImp));m->Login("user", "password");m->SendMessage("Hello, World!");// Clean updelete m;delete pcImp;
}
改進后的優點
- 減少類數量:通過橋接模式,將平臺相關實現和業務邏輯分離,減少了類的數量,此時類的數量為(1+n+m) < (1+n+m*n)。
- 減少代碼重復:將公共方法提取到實現類中,減少了代碼重復。
- 職責清晰:將平臺相關實現和業務邏輯分離,符合單一職責原則。
- 增加擴展性:通過裝飾者模式,能夠動態地添加新功能,而無需創建大量新的子類。
通過這些改進,可以顯著提高代碼的可維護性、可擴展性和清晰性。
疑問
在面向對象編程中,選擇成員變量的訪問級別時,需要考慮類的設計和使用場景。在這段代碼中,Messager
類的imp
成員變量被聲明為protected
,而不是private
或者public
,這是有特定原因的。
1. protected
訪問級別的意義
protected
訪問級別允許子類訪問和修改基類中的成員變量,同時對類外部保持不可見。這樣可以在保證類的封裝性的同時,允許子類進行適當的擴展和修改。
2. protected
的使用場景
在這段代碼中,Messager
類的設計是為了作為一個基類,它的主要作用是定義接口和提供基礎的實現,而具體的實現細節由其子類來完成。因此,將imp
成員變量設為protected
可以讓子類方便地訪問和操作這個變量,而無需通過公共接口來訪問。
具體分析代碼中的選擇
1. protected
訪問級別的好處
class Messager {
protected:MessagerImp* imp;
public:Messager(MessagerImp* imp) : imp(imp) {}virtual void Login(string username, string password) = 0;virtual void SendMessage(string message) = 0;virtual void SendPicture(Image image) = 0;virtual ~Messager() {}
};class MessagerLite : public Messager {
public:MessagerLite(MessagerImp* imp) : Messager(imp) {}void Login(string username, string password) override {imp->Connect();// Lite版特有的登錄邏輯}void SendMessage(string message) override {imp->WriteText();// Lite版特有的發送消息邏輯}void SendPicture(Image image) override {imp->DrawShape();// Lite版特有的發送圖片邏輯}
};
在這種設計下,MessagerLite
和其他子類可以直接訪問imp
,這使得子類可以輕松地調用平臺相關的實現方法,如Connect()
、WriteText()
和DrawShape()
。
2. 如果使用private
如果將imp
設為private
,子類將無法直接訪問imp
,需要通過公共的getter和setter方法。這增加了額外的復雜性,而且在某些情況下可能會影響性能和代碼的可讀性。
class Messager {
private:MessagerImp* imp;
public:Messager(MessagerImp* imp) : imp(imp) {}MessagerImp* getImp() { return imp; }virtual void Login(string username, string password) = 0;virtual void SendMessage(string message) = 0;virtual void SendPicture(Image image) = 0;virtual ~Messager() {}
};class MessagerLite : public Messager {
public:MessagerLite(MessagerImp* imp) : Messager(imp) {}void Login(string username, string password) override {getImp()->Connect();// Lite版特有的登錄邏輯}void SendMessage(string message) override {getImp()->WriteText();// Lite版特有的發送消息邏輯}void SendPicture(Image image) override {getImp()->DrawShape();// Lite版特有的發送圖片邏輯}
};
3. 如果使用public
如果將imp
設為public
,則任何外部代碼都可以直接訪問和修改imp
,這違反了封裝原則,容易導致錯誤和不必要的依賴。
class Messager {
public:MessagerImp* imp;Messager(MessagerImp* imp) : imp(imp) {}virtual void Login(string username, string password) = 0;virtual void SendMessage(string message) = 0;virtual void SendPicture(Image image) = 0;virtual ~Messager() {}
};// 外部代碼可以直接訪問 imp
Messager* m = new MessagerLite(new PCMessagerImp());
m->imp->Connect(); // 不推薦
總結
將MessagerImp* imp
設為protected
是為了在保持封裝性的同時,讓子類能夠方便地訪問和使用該成員變量。這種設計既保證了基類和子類之間的良好協作,又避免了將實現細節暴露給類的外部,提高了代碼的可維護性和擴展性。
要點總結
- 模式使用對象間的組合關系解耦合了。抽象和現實之間固有的綁定關系,使得抽象可實現可以沿著各自的維度來變化所,所謂抽象和實現沿著各自維度的變化,即子類化他們
- 強模式有時候類似于多繼承方案。但是多繼承方案往往違背單一職責,原則即一個類只有一個變化的原因,復用性比較差。橋模式是比多繼承方案更好的解決方法。
- 喬模橋模式的應用一般在兩個非常強的變化維度,有時一個內頁。有。多于兩個的變化維度,這時可以使用橋的擴展模式。