橋接模式 橋梁模式 bridge 結構型 設計模式(十二)

来源:https://www.cnblogs.com/noteless/archive/2018/11/30/10044061.html
-Advertisement-
Play Games

本文介紹了橋接模式Bridge也叫做橋梁模式,從思維的演進角度分析理解了橋梁模式的意義,給出了橋梁模式的意圖和結構,並且提供了java實現,通過對與適配器模式和裝飾器模式的對比加深了橋梁模式的理解,文末介紹了橋梁模式的使用場景以及註意事項。 ...


  橋接模式Bridge image_5c00db0d_60a6   Bridge 意為橋梁,橋接模式的作用就像橋梁一樣,用於把兩件事物連接起來  

意圖

將抽象部分與他的實現部分進行分離,使得他們都可以獨立的發展。 

意圖解析

依賴倒置原則要求程式要依賴於抽象介面,不要依賴於具體實現。 簡單的說就是要求對抽象進行編程,不要對實現進行編程,這樣就降低了客戶與實現模塊間的耦合 抽象 抽象就是將多個事物、實體中共同的概念提取出來 比如,一組共同特性的對象概念,可以提取出來類 如果一些類又具有共同的概念性聯繫,又可以提取出來抽象類和介面 實現 抽象的具體,就是實現 比如一個對象是一個類的實現,一個具體的子類是抽象父類的實現 
類的功能層次結構 按照依賴倒置原則,我們面向抽象進行編程 通常會使用介面或者抽象類用於描述功能概念 然後通過繼承進行功能概念的擴展,子類繼承父類,並且擴展父類以增加新的功能 類的實現層次結構 在基於功能層次結構的基礎上,需要對方法介面或者概念等進行具體實現 這些所有的實現就組成了類的實現層次結構
比如: 定義一個圖片編輯器imageEditor(介面) 分為windows和Linux兩個平臺(介面) 然後又分別有兩個實現類windowsImpl 以及LinuxImpl(實現類) image_5c00db0d_2922 圖中,紅框部分即為類的功能層次結構,藍色矩形框部分即為類的實現層次結構
對於類的層級結構不是很複雜的時候,我們可能經常會將類的功能層次結構和實現層次結構摻雜在一起 比如,你可能定義了一個抽象類A,然後他有兩個子類B和C,B是用於實現,另一個C卻是功能邏輯的擴展 當層次介面相對比較簡單的時候,摻雜在一起,還相對容易應對 當層次結構變得很複雜時,如果還是摻雜在一起將會變得非常難以處理 因為當你想要擴展產品功能邏輯時,你可能很難確定到底應該在類的哪一個層次結構中去擴展
我們將編輯器抽象提取出來Editor(介面) 又有圖形編輯器ImageEditor(介面)文本編輯器TextEditor(介面)視頻編輯器VideoEditor(介面) 又分別有windows和linux兩個版本的軟體 紅色框內為功能層次結構,藍色框內為實現層次介面 image_5c00db0e_7d15 上圖就是通過繼承的形式進行功能擴展與實現 可以看得出來,如果新增加一種新的編輯器,比如音頻AudioEditor 那麼可能需要新增加AudioWindowsEditor和AudioLinuxEditor以及他們的實現類AudioWindowsEditorImpl和AudioLinuxEditorImpl 除了新增的編輯器外,新增文件個數為4 如果,當新增加一種操作系統,比如 Os X 那麼,將需要根據現有的Editor類型 創建對應的三個Os X操作系統的介面(ImgXEditor,TextXEditor,VideoXEditor) 然後在創建與之對應的三個實現類(ImgXEditorImpl,TextXEditorImpl,VideoXEditorImpl) 除了新增的操作系統外,新增文件個數為6
這種採用多層繼承結構的形式,類的個數巨大 因為不僅僅有多種類型的Editor,設類型個數為X 又需要在多個操作系統平臺上進行實現,設平臺個數為Y 實現類的個數為X*Y
擴展時,如上例,個數又將會爆髮式的增長 隨之而來的就是維護、使用、運行等成本的增加
  繼承從一開始就把抽象角色和實現角色進行了綁定,是一種強關聯關係,並不符合組合復用原則 編譯時期就已經確定,不能夠在運行時期進行變動 而且,繼承將父類暴露給子類,如果父類發生變化,子類勢必將會受到波及影響,將不得不做出修改 不符合開閉原則
再有就是,對於每一個實現類,他即涉及具體類型的Editor又涉及平臺,比如ImgWindowsEditor 用於處理圖像img 又涉及到windows平臺,那麼涉及到img或者windows的修改,都可能會影響他導致修改 不符合單一職責原則
面對複雜繼承層次結構帶來的問題,所以,人們希望能夠 將抽象部分與他的實現部分進行分離,使得他們都可以獨立的發展。 這就是橋接模式的最初動機

向分離的演進

仔細觀察可以發現,之所以類會如此膨脹,擴展如此困難的原因就在於他不止一個維度 Editor類型以及不同平臺實現兩個維度 也正是這兩個維度導致了不符合單一職責原則 image_5c00db0e_3871
是否可以將這兩個維度進行分離?
如果能夠分離,也就意味著不會是完全使用繼承層次結構 因為繼承是強關聯,完全繼承,就不是分離 那麼,就需要考慮類的組合
如果能夠分離,將他們拆分為各自不同的維度,那麼就不會出現類的個數爆炸式增長的情況 因為一旦分離,就可以各自獨立發展 獨立發展,那麼增加一個Editor類型就是一個Editor類型 增加一個操作系統平臺,就只是增加一個操作系統平臺類型 不在爆炸增長
如果能夠進行分離,那麼他們各自負責自己不同的維度,職責將會更加單一
 

客戶端關註什麼?

客戶端程式的目的在於使用Editor,也就是Img、Text、Video的編輯 他其實並不在意到底是什麼平臺 而且,他也不應該在意,只有這樣才能夠跨平臺 但是,在我們的類層次結構中,卻偏偏的與平臺建立了強關聯
所以一種解決方案就是:  
客戶端面向Editor進行編程 Editor不關註平臺的細節,將與平臺相關的實現剝離開來 而平臺的實現部分,通過組合的方式,組合到Editor中來
    類似適配器模式(對象適配器模式),Editor作為目標對象Target 而與平臺相關聯的實現就是被適配的角色Adaptee 而每一個類型比如ImgEditor都是Adapter image_5c00db0e_6270 關於平臺相關的細節部分,通過組合的方式 如下圖所示Editor中含有指向Implementor的引用 涉及平臺相關性的處理,藉助於Implementor來完成 image_5c00db0e_b2c  

代碼示例

package bridge;
/**
* 編輯器的抽象類
* 內部包含implementor 對Editor的請求可以藉助於Implementor
*/
public abstract class Editor {
    protected Implementor implementor;
    public void setImplementor(Implementor implementor) {
        this.implementor = implementor;
    } 
    void doEdit(){
        implementor.systemEditor();
    }
}
package bridge;
public class ImgEditor extends Editor {
    @Override
    void doEdit() {
        System.out.println("ImgEditor working");
        implementor.systemEditor();
    }
}
package bridge;
public class TextEditor extends Editor {
    @Override
    void doEdit() {
        System.out.println("TextEditor working");
        implementor.systemEditor();
    }
}
package bridge;
public class VideoEditor extends Editor {
    @Override
    void doEdit() {
        System.out.println("VideoEditor working");
        implementor.systemEditor();
    }
}
以上代碼為上圖中的左半部分 image_5c00db0e_2bfb  
package bridge;
public interface Implementor {
void systemEditor();
}
package bridge;
public class WindowsImpl implements Implementor {
@Override
public void systemEditor() {
System.out.println("working on windows platform");
}
}
package bridge;
public class LinuxImpl implements Implementor {
 @Override
 public void systemEditor() {
  System.out.println("working on linux platform");
 }
}
以上為右半部分的實現 image_5c00db0e_af9
測試代碼
package bridge;
public class Test {
public static void main(String[] args) {
Editor editor = new ImgEditor();
editor.setImplementor(new WindowsImpl());
editor.doEdit();
}
}

 

image_5c00db0e_326e     在示例代碼中,創建了一個ImgEditor 然後動態的藉助於new WindowsImpl()  在windows平臺上執行編輯任務 在真正的藉助於平臺底層,執行平臺相關的代碼之前,還進行了一些其他的處理 比如上面的列印語句:     System.out.println("ImgEditor working");   這種使用方式,用戶關註的是Editor,不在與具體的平臺進行綁定 具體的平臺通過組合的形式組合到Editor中 在具體的使用到平臺相關的方法中,Editor依賴於內部的Implementor 進行處理 擴展時,也不會出現類文件個數爆炸增長的問題 可以相互獨立發展,這就是橋接模式      

結構

image_5c00db0e_14ad
抽象化角色Abstraction 抽象化給出定義,並保存一個對實現的引用 修正抽象化RefinedAbstraction 擴展抽象化角色,調整父類對抽象化的定義 實現角色Implementor 給出實現化角色的介面定義,但不給出具體的實現 這個介面不一定和Abstraction中的介面定義相同,實際上,可以完全不相同也沒關係 實現化角色僅僅給出底層操作抽象化角色給出基於底層操作,更高一層的抽象操作 比如底層是基於平臺的,更高一層則是ImgEditor這種 具體實現化角色ConcreteImplementor
給出實現化角色的具體實現代碼

  橋接模式的重點在於理解抽象化與實現化的概念含義 不要局限在java中定義一個介面A,然後定義一個實現類AImpl,然後override所有的方法 這種思維方式太狹隘了 抽象化在於針對於底層操作的更高一層抽象,更高一層的調用 就像上面的例子,ImgEditor真正的實現需要依賴底層具體的平臺,所以ImgEditor的doEdit方法是底層平臺實現的抽象化
千萬不要把抽象與實現局限在extends 和implements關鍵字的那種形式 應該認為,但凡是更高一層的調用,或者封裝,都可以認為是一種抽象與實現 也正是因為這種模式是extends 和implements關鍵字場景的進一步抽象 所以也被稱之為介面Interface模式
extends 和implements關鍵字的形式自然是抽象與實現 一個對象中的方法,藉助於另外的對象來實現,這也是一定程度上的“抽象化--實現化” 所以說適配器模式也是一定程度上的抽象化--實現化”
抽象化對象就像是手柄一樣,通過手柄來操縱委派給實現化角色,所以橋梁模式也被稱之為柄體模式 Handle and Body
抽象化等級結構中的方法,通過向對應的實現化對象委派實現自己的功能 這就意味著抽象化角色可以通過向不同的實現化對象委派就可以達到動態的轉換自己的功能的目的
在上面的示例中,我們使用 public void setImplementor(Implementor implementor) 進行Implementor的設置 一般經常使用工廠模式(方法)進行創建賦值  

橋梁模式與JDBC

jdbc的百度百科 image_5c00db0e_3248 JDBC是橋梁模式的典型應用 他將抽象化與實現化進行分離 image_5c00db0e_61e3 它為所有的關係資料庫提供了一個通用的訪問界面 提供了一組與具體廠家實現完全無關的介面 有了JDBC這一組通用介面 應用系統就可以不依賴資料庫引擎的具體細節而獨立的演化
一個應用系統動態的選擇一個合適的驅動器,然後通過驅動器向資料庫引擎發出指令 這就是抽象角色的行為委派給實現角色來完成任務
廠家的實現與JDBC之間,並沒有任何的靜態強關聯
其實很多其他形式的驅動又何嘗不是如此? 比如Office辦公軟體列印不需要關註於具體的印表機廠家型號 會有統一的驅動為我們進行處理  

模式對比

與適配器區別

image_5c00db0e_797d
上面說到橋接模式類似適配器模式 而且,某種程度上講,適配器模式也符合“抽象化---實現化”的概念 而且,從上面的結構圖中,可以看得出來,橋接模式與對象適配器模式的相似程度 他們都擁有抽象的概念角色 Abstraction和Target 它們都擁有具體的客戶端需要直接面對的角色 RefinedAbstraction和Adapter 他們都有工作需要委托給內部的“工作人員”Implementor 和 Adaptee 他們都依賴於“抽象”與“實現”的概念
那麼到底有什麼區別呢? 適配器模式的主要目的是讓因為介面不相容而不能互相工作的類能夠一起工作 換句話說就是他們本身不同,我用“紐帶” Adapter將他們連接起來 而橋接模式則是將原本或許緊密結合在一起的抽象與實現,進行分離 使她們能夠各自獨立的發展,是把連接在一起的兩個事物,拆分開來 然後用“紐帶”“橋梁”(也就是對象的引用)將他們連接起來
適配器模式就好比張三和王五不認識,李四介紹他們認識 橋梁模式好比張三和王五成天黏在一起活幹得不好太亂套,李四說以後我作為介面人,你倆各乾各的吧 雖然看起來都是兩個人,中間一個聯繫人,但是含義卻是完全不同  

與裝飾器區別

裝飾器模式中,使用組合而不是繼承來對類的功能進行擴展,避免了類的個數的爆炸增長,與橋梁模式的結果不約而同 都解決了類爆炸增長的問題,都避免了過多的沒必要的子類
裝飾器模式側重於功能的動態增加,將額外的功能提取到子類中 通過不同的排列組合,形成一個遞歸的調用方式,以動態的增加各部分的功能
橋梁模式是將原本系統中的實現細節抽取出來,比如原來抽象概念與實例化全部都是一個類層次結構中 把所有的實現細節,比如示例中的平臺相關實現,抽取出來,進行分離 達到抽象與實現分離的目的
所以雖然他們都可以解決子類爆炸式增長、不易擴展的問題 但是他們的出發點完全不同 一個關註於功能的動態擴展組合 一個關註於抽象與實現的分離,獲得更多的靈活性

總結

場景

如果一個系統要求抽象化角色和具體化角色之間增加更多的靈活性 避免在兩個層次之間建立靜態的聯繫 也就是實現化角色的改變,不會影響客戶端,完全透明的
如果系統需要在多個抽象化角色和實現化角色之間進行動態的耦合 也就是要求能夠動態的組合
如果使用多層繼承結構進行處理時,就可以考慮使用橋接模式 尤其是一個類存在兩個或者多個變化的維度,而且這兩個維度也可能需要動態的擴展
總之,當你需要抽象化和是實現化進行解耦時,你就可以考慮橋梁模式 解耦就會變得透明不會互相影響能夠獨立發展,解耦就能夠動態的組合,解耦了就不會有靜態的聯繫  

優點

提高了擴展性,多層繼承的良好替代方案 將抽象與實現進行解耦,更加符合單一職責原則 組合復用原則以及開閉原則  

註意點

想要使用橋接模式,必然要理清楚你面對的需求中抽象與實現的部分,才能更好的進行運用。 只要具備抽象與實現分離的相關需要,都可以考慮橋梁模式
前面描述的多個維度不同發展,多層次的抽象,是橋梁模式的更高級別的運用,必須要先分析清楚變化的維度 而且最重要的就是分析出整個類層次結構中的“抽象化”部分和“實現化”部分 我們的Editor示例中,ImgEditor TextEditor才是客戶端程式關註的,他們不希望關註於具體平臺 JDBC中,客戶端程式關註的是資料庫的查詢操作行為,而不希望關註資料庫的細節 所以你必須找準到底誰是抽象化
橋接模式的場景理解起來略微有點費神 但是他的根本邏輯卻是非常簡單,那就是抽象的概念功能與具體的實現進行分離 橋接模式是面向抽象編程--依賴倒置原則的具體體現 並且使用組合的形式,解決了多層繼承結構中的一些問題   原文地址:橋接模式 橋梁模式 bridge 結構型 設計模式(十二)

 


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

-Advertisement-
Play Games
更多相關文章
  • 1.針對錶單的 form 表單 input 輸入框 select 下拉列表 textarea 文本域 type 類型 radio 單選框 checkbox 多選框 password 密碼框 button 普通按鈕 text 文本框標簽 submit 提交按鈕 reset 重置 表單的事件: onch ...
  • Progress進度條組件 html <! 最外層 <! 線形進度條 <! 進度條外部背景;strokeWidth:文檔中說是寬度,這裡是高度呀 <! 進度條內部百分比 <! 線形進度條內部文字 {{percentage}}% <! 環形進度條 <! 進度條外面文字內容 <! 進度條當前狀態stat ...
  • 1.針對錶單的 form input select textarea type=”radio/checkbox/password/button/text/submit/reset/” 表單的事件 onchange 當表單內容被修改時觸發的事件 onfocus 獲取焦點事件 onblur 失去焦點事件 ...
  • 還原車禍現場 功能類似於百度搜索,搜索框輸入內容,下拉框顯示候選項,點擊候選項就選擇候選項,然後下拉框隱藏,點擊外面就直接隱藏下拉框,於是我寫了以下代碼 先去請求數據,然後渲染列表,然後監聽候選項點擊,最後blur的時候隱藏下拉框,覺得自己寫的很完美,於是便興衝衝的去測試,但是一測試我發現了大問題, ...
  • from django.db import models # Create your models here. class Classes(models.Model): ''' 班級表 ''' title=models.CharField(max_length=32) m=models.ManyTo... ...
  • 問題來源 互聯網項目通常都是大用戶量,大併發,因此從技術架構上大多採用分散式架構構建成大型分散式系統,SOA或者是微服務,一個請求涉及到多個子系統,如果某個請求的處理不正常,怎麼排查定位問題呢?如果沒有合適的手段,排查問題無異大海撈針,為了提高解決問題的效率,迫切需要有一個技術手段能跟蹤整個處理環節 ...
  • 觀察者模式(Observer Pattern)也稱發佈訂閱模式,它是一種在項目中經常使用的模式。 定義: 定義對象間一種一對多的依賴關係,使得每當一個對象改變狀態,則所有依賴於它的對象都會得到通知並被自動更新。 觀察者模式的類圖如下所示。 觀察者模式具體有以下4個角色。 抽象主題(Subject)角 ...
  • 參考於 : 大話設計模式 馬士兵設計模式視頻 寫在開頭: 橋接模式主要用於一件事物分成了兩個維度,進行排列組合,比如禮物,可以分成優雅的禮物(抽象),花(具體),排列組合優雅的花! 1.為什麼使用橋接模式 小丁追小彭,送禮物必不可少。面向對象的思維,如何去實現這個禮物,先定義一個禮物的介面或抽象類, ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...