趣谈设计模式 | 桥接模式(Bridge):将抽象与实现分离
文章目錄
- 案例:跨平臺應用開發
- 橋接模式
- 總結
- 完整代碼與文檔
案例:跨平臺應用開發
A公司最近準備開發一組應用合集,目前包括了視頻播放器、音樂播放器、文本播放器三種應用,但是在發售前,他們遇到了困難。
由于目前計算機系統繁多,且彼此之間大多不相互兼容,所以還需要針對不同平臺,為應用進行處理。
在初步的設計中,A公司將平臺抽象為接口,然后針對不同平臺來實現應用,設計圖如下
這一設計乍一看沒有什么問題,但是復用性卻極差。
例如當A公司新開發了一個PDF閱讀器時,就需要分別在這三個平臺下都實現一個PDF閱讀器,可由于這些PDF閱讀器之間相差不大,代碼的復用程度低。
而如果A公司此時想要兼容更多的平臺,例如新加入了Android平臺,就需要新增一個Android類,并且在其下實現所有之前的應用,這樣的設計不僅代碼復用性差,靈活性也不足。
于是A公司想到了第二種方案,按照具體應用的實現來進行分類,設計圖如下
但是由于這一設計的本質還是依賴于繼承,所以它無論是平臺,還是具體的應用發生了拓展,它都仍然存在著上面的問題。
引用GOF的觀點
對象的繼承關系是在編譯時就定義好了的,所以無法在運行時改變從父類繼承的實現。子類的實現與它的父類有非常緊密的依賴關系,以至于父類實現中的任何變化都必然會導致子類發生變化。當你需要復用子類時,如果繼承下來的實現不適合解決性的問題,則父類必須重寫或被其他更適合的類替換。這種依賴關系限制了靈活性并最終限制了復用性。
那如何解決這個問題呢?我們可以考慮用聚合代替繼承
在上面的設計中,最大的問題就是平臺以及應用之間存在著強耦合的關系,無論我們新增的是平臺還是應用,都會對彼此造成影響。
既然如此,我們為何不將他們分離呢?
此時,平臺和應用之間相互獨立,當我們要新增平臺,只需要去實現平臺接口,應用也同理。
那么接下來如何處理平臺與應用之間的關系呢?首先我們要知道,雖然平臺中包含了各種應用,但是應用本身并不是平臺的一部分,那么它們其實是has-a的關系,所以我們可以使用聚合(即A包含B,但B不是A的一部分)來實現
通過聚合,既保證了平臺與應用兩者的獨立變化,同時又保證了兩者之間的包含關系。
聚合像橋梁一樣將兩者連接到一塊,所以這種設計模式又叫做橋接模式
橋接模式
橋接模式將抽象部分與它的實現部分分離,使他們都可以獨立地變化
什么叫將抽象和實現分離呢?
這里的抽象指的并不是抽象類或者接口,而是被抽象出來的一套“類庫”,它只包含骨架代碼,真正的業務邏輯需要委托給定義中的“實現”來完成。我們這里所說的實現也絕非接口的實現類,而是一套獨立的“類庫”。“抽象”和“實現”獨立開發,通過對象之間的組合關系,組裝在一起。
橋接模式由以下部分組成
- Abstraction(抽象):使用實現提供的接口來定義基本功能接口
- Implementor(實現):提供了用于抽象的接口,它是一個抽象類或者接口。
- RefinedAbstraction(提煉后的抽象):作為抽象的子類,增加新的功能,也就是增加新的接口(方法)
- ConcreteImplementor(具體的實現):作為實現的子類,通過實現具體方法來實現接口
類圖如下
根據橋接模式來改造我們的設計,如下圖
首先分別實現“抽象”:平臺,以及“實現”:軟件
#pragma onceclass Implementor { public:virtual ~Implementor() = default;virtual void run() = 0; }; #pragma once #include"Implementor.hpp"class Abstraction { public:virtual ~Abstraction() = default;void setImplementor(Implementor* implementor){_implementor = implementor;}virtual void run(){if(_implementor != nullptr){_implementor->run();}}protected:Implementor* _implementor = nullptr; };接著實現我們具體的實現,以及提煉后的抽象
#pragma once #include<iostream> #include"Implementor.hpp"class Music : public Implementor { public:void run() override{std::cout << "啟動音樂播放器" << std::endl;} };class Vedio : public Implementor { public:void run() override{std::cout << "啟動視頻播放器" << std::endl;} };class Text : public Implementor { public:void run() override{std::cout << "啟動文本閱讀器" << std::endl;} }; #pragma once #include<iostream> #include"Abstraction.hpp"class Linux : public Abstraction { public:void run() override{std::cout << "Linux系統:";_implementor->run();} };class Windows : public Abstraction { public:void run() override{std::cout << "Windows系統:";_implementor->run();} };class Mac : public Abstraction { public:void run() override{std::cout << "Mac系統:";_implementor->run();} };測試代碼
#include"RefinedAbstraction.hpp" #include"ConcreteImplementor.hpp"using namespace std;int main() {Abstraction* linux = new Linux;Abstraction* windows = new Windows;Abstraction* mac = new Mac;linux->setImplementor(new Vedio);windows->setImplementor(new Vedio);linux->run();windows->run();linux->setImplementor(new Music);linux->run();mac->setImplementor(new Text);mac->run();return 0; }運行結果如下
在這種設計下,當我們需要拓展平臺或者應用的時候,就只需要去實現對應的抽象類即可
總結
要點
- 將抽象與實現分離,起到了解耦合的作用
- 抽象和實現可以獨立拓展,不會影響到對方
- 實現細節對客戶透明
- 增加了設計的復雜度,由于聚合關系建立在抽象層,要求開發者針對抽象進行設計與編程。
應用場景
- 適合使用在需要跨越多平臺、型號的設計
- 需要用不同的方法改變接口與實現時
- 一個類存在兩個獨立變化的維度時
完整代碼與文檔
如果有需要完整代碼或者markdown文檔的同學可以點擊下面的github鏈接
github
總結
以上是生活随笔為你收集整理的趣谈设计模式 | 桥接模式(Bridge):将抽象与实现分离的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 趣谈设计模式 | 职责链模式(Chain
- 下一篇: Linux 内存管理 | 地址映射:分段