C++橋接模式【轉】

来源:https://www.cnblogs.com/linhaostudy/archive/2018/02/05/8416350.html
-Advertisement-
Play Games

https://www.cnblogs.com/jiese/p/3164940.html 將抽象部份與它的實現部份分離,使它們都可以獨立地變化。 橋接模式號稱設計模式中最難理解的模式之一,關鍵就是這個抽象和實現的分離非常讓人奇怪,大部分人剛看到這個定義的時候都會認為實現就是繼承自抽象,那怎麼可能將他 ...


https://www.cnblogs.com/jiese/p/3164940.html

 

將抽象部份與它的實現部份分離,使它們都可以獨立地變化。

橋接模式號稱設計模式中最難理解的模式之一,關鍵就是這個抽象和實現的分離非常讓人奇怪,大部分人剛看到這個定義的時候都會認為實現就是繼承自抽象,那怎麼可能將他們分離呢。

 《大話設計模式》中就Bridge模式的解釋:

手機品牌和軟體是兩個概念,不同的軟體可以在不同的手機上,不同的手機可以有相同的軟體,兩者都具有很大的變動性。如果我們單獨以手機品牌或手機軟體為基類來進行繼承擴展的話,無疑會使類的數目劇增並且耦合性很高,(如果更改品牌或增加軟體都會增加很多的變動)兩種方式的結構如下:

 

所以將兩者抽象出來兩個基類分別是PhoneBrand和PhoneSoft,那麼在品牌類中聚合一個軟體對象的基類將解決軟體和手機擴展混亂的問題,這樣兩者的擴展就相對靈活,剪短了兩者的必要聯繫,結構圖如下:

 

這樣擴展品牌和軟體就相對靈活獨立,達到解耦的目的!

 UML結構圖如下:

 

抽象基類及介面:

1、Abstraction::Operation():定義要實現的操作介面

2、AbstractionImplement::Operation():實現抽象類Abstaction所定義操作的介面,由其具體派生類ConcreteImplemenA、ConcreteImplemenA或者其他派生類實現。

3、在Abstraction::Operation()中根據不同的指針多態調用AbstractionImplement::Operation()函數。

 理解:

Bridge用於將表示和實現解耦,兩者可以獨立的變化.在Abstraction類中維護一個AbstractionImplement類指針,需要採用不同的實現方式的時候只需要傳入不同的AbstractionImplement派生類就可以了.

Bridge的實現方式其實和Builde十分的相近,可以這麼說:本質上是一樣的,只是封裝的東西不一樣罷了.兩者的實現都有如下的共同點:

抽象出來一個基類,這個基類裡面定義了共有的一些行為,形成介面函數(對介面編程而不是對實現編程),這個介面函數在Buildier中是BuildePart函數在Bridge中是Operation函數;

其次,聚合一個基類的指針,如Builder模式中Director類聚合了一個Builder基類的指針,而Brige模式中Abstraction類聚合了一個AbstractionImplement基類的指針(優先採用聚合而不是繼承);

而在使用的時候,都把對這個類的使用封裝在一個函數中,在Bridge中是封裝在Director::Construct函數中,因為裝配不同部分的過程是一致的,而在Bridge模式中則是封裝在Abstraction::Operation函數中,在這個函數中調用對應的AbstractionImplement::Operation函數.就兩個模式而言,Builder封裝了不同的生成組成部分的方式,而Bridge封裝了不同的實現方式.

橋接模式就將實現與抽象分離開來,使得RefinedAbstraction依賴於抽象的實現,這樣實現了依賴倒轉原則,而不管左邊的抽象如何變化,只要實現方法不變,右邊的具體實現就不需要修改,而右邊的具體實現方法發生變化,只要介面不變,左邊的抽象也不需要修改。

 

常用的場景
1.當一個對象有多個變化因素的時候,考慮依賴於抽象的實現,而不是具體的實現。如上面例子中手機品牌有2種變化因素,一個是品牌,一個是功能。

2.當多個變化因素在多個對象間共用時,考慮將這部分變化的部分抽象出來再聚合/合成進來,如上面例子中的通訊錄和游戲,其實是可以共用的。

3.當我們考慮一個對象的多個變化因素可以動態變化的時候,考慮使用橋接模式,如上面例子中的手機品牌是變化的,手機的功能也是變化的,所以將他們分離出來,獨立的變化。

優點
1.將實現抽離出來,再實現抽象,使得對象的具體實現依賴於抽象,滿足了依賴倒轉原則。

2.將可以共用的變化部分,抽離出來,減少了代碼的重覆信息。

3.對象的具體實現可以更加靈活,可以滿足多個因素變化的要求。

缺點
1.客戶必須知道選擇哪一種類型的實現。

 

設計中有超過一維的變化我們就可以用橋模式。如果只有一維在變化,那麼我們用繼承就可以圓滿的解決問題。

 代碼如下:

Abstraction.h:

 1 #ifndef _ABSTRACTION_H_
 2 #define _ABSTRACTION_H_
 3 
 4 class AbstractionImplement;
 5 
 6 class Abstraction
 7 {
 8 public:
 9     virtual void Operation()=0;//定義介面,表示該類所支持的操作
10     virtual ~Abstraction();
11 protected:
12     Abstraction();
13 };
14 
15 class RefinedAbstractionA:public Abstraction
16 {
17 public:
18     RefinedAbstractionA(AbstractionImplement* imp);//構造函數
19     virtual void Operation();//實現介面
20     virtual ~RefinedAbstractionA();//析構函數
21 private:
22     AbstractionImplement* _imp;//私有成員
23 };
24 
25 class RefinedAbstractionB:public Abstraction
26 {
27 public:
28     RefinedAbstractionB(AbstractionImplement* imp);//構造函數
29     virtual void Operation();//實現介面
30     virtual ~RefinedAbstractionB();//析構函數
31 private:
32     AbstractionImplement* _imp;//私有成員
33 };
34 #endif

 

 

Abstraction.cpp:

 1 #include "Abstraction.h"
 2 #include "AbstractionImplement.h"
 3 #include <iostream>
 4 
 5 using namespace std;
 6 
 7 Abstraction::Abstraction()
 8 {}
 9 
10 Abstraction::~Abstraction()
11 {}
12 
13 RefinedAbstractionA::RefinedAbstractionA(AbstractionImplement* imp)
14 {
15     this->_imp = imp;
16 }
17 
18 RefinedAbstractionA::~RefinedAbstractionA()
19 {
20     delete this->_imp;
21     this->_imp = NULL;
22 }
23 
24 void RefinedAbstractionA::Operation()
25 {
26     cout << "RefinedAbstractionA::Operation" << endl;
27     this->_imp->Operation();
28 }
29 
30 RefinedAbstractionB::RefinedAbstractionB(AbstractionImplement* imp)
31 {
32     this->_imp = imp;
33 }
34 
35 RefinedAbstractionB::~RefinedAbstractionB()
36 {
37     delete this->_imp;
38     this->_imp = NULL;
39 }
40 
41 void RefinedAbstractionB::Operation()
42 {
43     cout << "RefinedAbstractionB::Operation" << endl;
44     this->_imp->Operation();
45 }

 

AbstractImplement.h:

 1 #ifndef _ABSTRACTIONIMPLEMENT_H_
 2 #define _ABSTRACTIONIMPLEMENT_H_
 3 
 4 //抽象基類,定義了實現的介面
 5 class AbstractionImplement
 6 {
 7 public:
 8     virtual void Operation()=0;//定義操作介面
 9     virtual ~AbstractionImplement();
10 protected:
11     AbstractionImplement();
12 };
13 
14 // 繼承自AbstractionImplement,是AbstractionImplement的不同實現之一
15 class ConcreteAbstractionImplementA:public AbstractionImplement
16 {
17 public:
18     ConcreteAbstractionImplementA();
19     void Operation();//實現操作
20     ~ConcreteAbstractionImplementA();
21 protected:
22 };
23 
24 // 繼承自AbstractionImplement,是AbstractionImplement的不同實現之一
25 class ConcreteAbstractionImplementB:public AbstractionImplement
26 {
27 public:
28     ConcreteAbstractionImplementB();
29     void Operation();//實現操作
30     ~ConcreteAbstractionImplementB();
31 protected:
32 };
33 #endif

 

AbstractImplement.cpp:

 1 #include "AbstractionImplement.h"
 2 #include <iostream>
 3 
 4 using namespace std;
 5 
 6 AbstractionImplement::AbstractionImplement()
 7 {}
 8 
 9 AbstractionImplement::~AbstractionImplement()
10 {}
11 
12 ConcreteAbstractionImplementA::ConcreteAbstractionImplementA()
13 {}
14 
15 ConcreteAbstractionImplementA::~ConcreteAbstractionImplementA()
16 {}
17 
18 void ConcreteAbstractionImplementA::Operation()
19 {
20     cout << "ConcreteAbstractionImplementA Operation" << endl;
21 }
22 
23 ConcreteAbstractionImplementB::ConcreteAbstractionImplementB()
24 {}
25 
26 ConcreteAbstractionImplementB::~ConcreteAbstractionImplementB()
27 {}
28 
29 void ConcreteAbstractionImplementB::Operation()
30 {
31     cout << "ConcreteAbstractionImplementB Operation" << endl;
32 }

 

 

main.cpp:

 1 #include "Abstraction.h"
 2 #include "AbstractionImplement.h"
 3 #include <iostream>
 4 
 5 using namespace std;
 6 
 7 int main()
 8 {
 9     /* 將抽象部分與它的實現部分分離,使得它們可以獨立地變化
10 
11     1、抽象Abstraction與實現AbstractionImplement分離;
12 
13     2、抽象部分Abstraction可以變化,如new RefinedAbstractionA(imp)、new RefinedAbstractionB(imp2);
14 
15     3、實現部分AbstractionImplement也可以變化,如new ConcreteAbstractionImplementA()、new ConcreteAbstractionImplementB();
16 
17     */
18 
19     AbstractionImplement* imp = new ConcreteAbstractionImplementA();        //實現部分ConcreteAbstractionImplementA
20     Abstraction* abs = new RefinedAbstractionA(imp);                        //抽象部分RefinedAbstractionA
21     abs->Operation();
22 
23     cout << "-----------------------------------------" << endl;
24 
25     AbstractionImplement* imp1 = new ConcreteAbstractionImplementB();        //實現部分ConcreteAbstractionImplementB
26     Abstraction* abs1 = new RefinedAbstractionA(imp1);                        //抽象部分RefinedAbstractionA
27     abs1->Operation();
28 
29     cout << "-----------------------------------------" << endl;
30 
31     AbstractionImplement* imp2 = new ConcreteAbstractionImplementA();        //實現部分ConcreteAbstractionImplementA
32     Abstraction* abs2 = new RefinedAbstractionB(imp2);                        //抽象部分RefinedAbstractionB
33     abs2->Operation();
34 
35     cout << "-----------------------------------------" << endl;
36 
37     AbstractionImplement* imp3 = new ConcreteAbstractionImplementB();        //實現部分ConcreteAbstractionImplementB
38     Abstraction* abs3 = new RefinedAbstractionB(imp3);                        //抽象部分RefinedAbstractionB
39     abs3->Operation();
40 
41     cout << endl;
42     return 0;
43 }

 

 

代碼說明:
Bridge模式將抽象和實現分別獨立實現,在代碼中就是Abstraction類和AbstractionImplement類。

使用組合(委托)的方式將抽象和實現徹底地解耦,這樣的好處是抽象和實現可以分別獨立地變化,系統的耦合性也得到了很好的降低。
GoF的那句話中的“實現”該怎麼去理解:“實現”特別是和“抽象”放在一起的時候我們“預設”的理解是“實現”就是“抽象”的具體子類的實現,但是這裡GoF所謂的“實現”的含義不是指抽象基類的具體子類對抽象基類中虛函數(介面)的實現,是和繼承結合在一起的。而這裡的“實現”的含義指的是怎麼去實現用戶的需求,並且指的是通過組合(委托)的方式實現的,因此這裡的實現不是指的繼承基類、實現基類介面,而是指的是通過對象組合實現用戶的需求。

實際上上面使用Bridge模式和使用帶來問題方式的解決方案的根本區別在於是通過繼承還是通過組合的方式去實現一個功能需求。

備註:

由於實現的方式有多種,橋接模式的核心就是把這些實現獨立出來,讓他們各自變化。

將抽象部分與它的實現部分分離:實現系統可能有多角度(維度)分類,每一種分類都可能變化,那麼就把這種多角度分離出來讓它們獨立變化,減少它們之間的耦合。

在發現需要多角度去分類實現對象,而只用繼承會造成大量的類增加,不能滿足開放-封閉原則時,就要考慮用Bridge橋接模式了。

合成/聚合復用原則:儘量使用合成/聚合,精良不要使用類繼承。
優先使用對象的合成/聚合將有助於保持每個類被封裝,並被集中在單個任務上。這樣類和類繼承層次會保持較小規模,並且不太可能增長為不可控制的龐然大物。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 上拉載入的思路 1 上拉載入是要把屏幕拉到最底部的時候觸發ajax事件請求數據 2.所有要獲取屏幕的高度 文檔的高度 和滾動的高度 下麵的代碼是已經做好了相容的可以直接拿來用 3.首先要預設載入第一頁,在window.onload調用upDown這個方法 4.當頁面滾到底部的時候觸發up()這個方法 ...
  • 前言:最近要使用百度地圖實現樓盤可視化的功能,因此最基礎的功能就是將地圖網格化以後實現不同地域的樓盤劃分; 1,自行去百度地圖的開放平臺申請秘鑰哈,這裡我就把自己的秘鑰貼出來了;ak=A3CklGvnFOjkAzKzay2dySgfdig0GKz4 2,新建一個簡單頁面,下麵我把自己的頁面貼出來 3 ...
  • 本文最初發表於 "博客園" ,併在 "GitHub" 上持續更新 前端的系列文章 。歡迎在GitHub上關註我,一起入門和進階前端。 以下是正文。 前言 jQuery提供的一組網頁中常見的動畫效果,這些動畫是標準的、有規律的效果;同時還提供給我們了自定義動畫的功能。 顯示動畫 方式一: 解釋:無參數 ...
  • 1、window.screen.height window.screen.height:設備顯示屏的高度 (1)解析度為1080px的顯示屏 (2)手機屏 2、window.screen.availHeight 屏幕的可用高度 (1)解析度為1080px的顯示屏 (2)手機屏 3、document. ...
  • 容器的屬性 項目的屬性 ...
  • target:指定框架集中的哪個框架來裝在另一個資源,該屬性可以是_self、_blank、_top、_parent四個值,分別代表使用自身、新視窗、頂層框架、父框架來裝載新資源。 alt只是在圖片無法載入的還是才會顯示出提示文字,如果想讓圖片無論怎樣都顯示,用title 如果希望獲得最佳表單性能, ...
  • 對於剛接觸ubuntu的同學來說,一切都是新的,一切都是那麼熟悉而又不熟悉的.不管是作為一個前端工程師還是一個後端工程師,我相信大家知道nodejs,但是如果希望自己能夠在ubuntu上面使用nodejs,是需要給點功夫去做的. 當然對於一個ubuntuer來說 這個命令就再熟悉不過了,也是經常用的 ...
  • 登錄認證幾乎是任何一個系統的標配,web 系統、APP、PC 客戶端等,好多都需要註冊、登錄、授權認證。 場景說明 以一個電商系統,假設淘寶為例,如果我們想要下單,首先需要註冊一個賬號。擁有了賬號之後,我們需要輸入用戶名(比如手機號或郵箱)、密碼完成登錄過程。之後如果你在一段時間內再次進入系統,是不 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...