本文介紹了橋接模式Bridge也叫做橋梁模式,從思維的演進角度分析理解了橋梁模式的意義,給出了橋梁模式的意圖和結構,並且提供了java實現,通過對與適配器模式和裝飾器模式的對比加深了橋梁模式的理解,文末介紹了橋梁模式的使用場景以及註意事項。 ...
橋接模式Bridge
![image_5c00db0d_60a6 image_5c00db0d_60a6](https://img2018.cnblogs.com/blog/897393/201811/897393-20181130144225008-533579708.png)
意圖
將抽象部分與他的實現部分進行分離,使得他們都可以獨立的發展。意圖解析
依賴倒置原則要求程式要依賴於抽象介面,不要依賴於具體實現。 簡單的說就是要求對抽象進行編程,不要對實現進行編程,這樣就降低了客戶與實現模塊間的耦合 抽象 抽象就是將多個事物、實體中共同的概念提取出來 比如,一組共同特性的對象概念,可以提取出來類 如果一些類又具有共同的概念性聯繫,又可以提取出來抽象類和介面 實現 抽象的具體,就是實現 比如一個對象是一個類的實現,一個具體的子類是抽象父類的實現類的功能層次結構 按照依賴倒置原則,我們面向抽象進行編程 通常會使用介面或者抽象類用於描述功能概念 然後通過繼承進行功能概念的擴展,子類繼承父類,並且擴展父類以增加新的功能 類的實現層次結構 在基於功能層次結構的基礎上,需要對方法介面或者概念等進行具體實現 這些所有的實現就組成了類的實現層次結構
比如: 定義一個圖片編輯器imageEditor(介面) 分為windows和Linux兩個平臺(介面) 然後又分別有兩個實現類windowsImpl 以及LinuxImpl(實現類)
![image_5c00db0d_2922 image_5c00db0d_2922](https://img2018.cnblogs.com/blog/897393/201811/897393-20181130144226867-962042412.png)
對於類的層級結構不是很複雜的時候,我們可能經常會將類的功能層次結構和實現層次結構摻雜在一起 比如,你可能定義了一個抽象類A,然後他有兩個子類B和C,B是用於實現,另一個C卻是功能邏輯的擴展 當層次介面相對比較簡單的時候,摻雜在一起,還相對容易應對 當層次結構變得很複雜時,如果還是摻雜在一起將會變得非常難以處理 因為當你想要擴展產品功能邏輯時,你可能很難確定到底應該在類的哪一個層次結構中去擴展
我們將編輯器抽象提取出來Editor(介面) 又有圖形編輯器ImageEditor(介面)文本編輯器TextEditor(介面)視頻編輯器VideoEditor(介面) 又分別有windows和linux兩個版本的軟體 紅色框內為功能層次結構,藍色框內為實現層次介面
![image_5c00db0e_7d15 image_5c00db0e_7d15](https://img2018.cnblogs.com/blog/897393/201811/897393-20181130144227827-1006516198.png)
這種採用多層繼承結構的形式,類的個數巨大 因為不僅僅有多種類型的Editor,設類型個數為X 又需要在多個操作系統平臺上進行實現,設平臺個數為Y 實現類的個數為X*Y |
擴展時,如上例,個數又將會爆髮式的增長 隨之而來的就是維護、使用、運行等成本的增加 |
再有就是,對於每一個實現類,他即涉及具體類型的Editor又涉及平臺,比如ImgWindowsEditor 用於處理圖像img 又涉及到windows平臺,那麼涉及到img或者windows的修改,都可能會影響他導致修改 不符合單一職責原則
面對複雜繼承層次結構帶來的問題,所以,人們希望能夠 將抽象部分與他的實現部分進行分離,使得他們都可以獨立的發展。 這就是橋接模式的最初動機
向分離的演進
仔細觀察可以發現,之所以類會如此膨脹,擴展如此困難的原因就在於他不止一個維度
Editor類型以及不同平臺實現兩個維度
也正是這兩個維度導致了不符合單一職責原則
![image_5c00db0e_3871 image_5c00db0e_3871](https://img2018.cnblogs.com/blog/897393/201811/897393-20181130144228699-33688998.png)
是否可以將這兩個維度進行分離?
如果能夠分離,也就意味著不會是完全使用繼承層次結構 因為繼承是強關聯,完全繼承,就不是分離 那麼,就需要考慮類的組合 |
如果能夠分離,將他們拆分為各自不同的維度,那麼就不會出現類的個數爆炸式增長的情況 因為一旦分離,就可以各自獨立發展 獨立發展,那麼增加一個Editor類型就是一個Editor類型 增加一個操作系統平臺,就只是增加一個操作系統平臺類型 不在爆炸增長 |
如果能夠進行分離,那麼他們各自負責自己不同的維度,職責將會更加單一 |
客戶端關註什麼?
客戶端程式的目的在於使用Editor,也就是Img、Text、Video的編輯 他其實並不在意到底是什麼平臺 而且,他也不應該在意,只有這樣才能夠跨平臺 但是,在我們的類層次結構中,卻偏偏的與平臺建立了強關聯所以一種解決方案就是:
客戶端面向Editor進行編程 Editor不關註平臺的細節,將與平臺相關的實現剝離開來 而平臺的實現部分,通過組合的方式,組合到Editor中來 |
![image_5c00db0e_6270 image_5c00db0e_6270](https://img2018.cnblogs.com/blog/897393/201811/897393-20181130144229081-456075292.png)
![image_5c00db0e_b2c image_5c00db0e_b2c](https://img2018.cnblogs.com/blog/897393/201811/897393-20181130144230022-1276907443.png)
代碼示例
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 image_5c00db0e_2bfb](https://img2018.cnblogs.com/blog/897393/201811/897393-20181130144230457-798114798.png)
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 image_5c00db0e_af9](https://img2018.cnblogs.com/blog/897393/201811/897393-20181130144230874-783406762.png)
測試代碼
package bridge; public class Test { public static void main(String[] args) { Editor editor = new ImgEditor(); editor.setImplementor(new WindowsImpl()); editor.doEdit(); } }
![image_5c00db0e_326e image_5c00db0e_326e](https://img2018.cnblogs.com/blog/897393/201811/897393-20181130144231282-871387621.png)
結構
![image_5c00db0e_14ad image_5c00db0e_14ad](https://img2018.cnblogs.com/blog/897393/201811/897393-20181130144232168-726545886.png)
抽象化角色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 image_5c00db0e_3248](https://img2018.cnblogs.com/blog/897393/201811/897393-20181130144232949-1814908200.png)
![image_5c00db0e_61e3 image_5c00db0e_61e3](https://img2018.cnblogs.com/blog/897393/201811/897393-20181130144233433-490464571.png)
一個應用系統動態的選擇一個合適的驅動器,然後通過驅動器向資料庫引擎發出指令 這就是抽象角色的行為委派給實現角色來完成任務
廠家的實現與JDBC之間,並沒有任何的靜態強關聯
其實很多其他形式的驅動又何嘗不是如此? 比如Office辦公軟體列印不需要關註於具體的印表機廠家型號 會有統一的驅動為我們進行處理
模式對比
與適配器區別
![image_5c00db0e_797d image_5c00db0e_797d](https://img2018.cnblogs.com/blog/897393/201811/897393-20181130144233986-1992031057.png)
上面說到橋接模式類似適配器模式 而且,某種程度上講,適配器模式也符合“抽象化---實現化”的概念 而且,從上面的結構圖中,可以看得出來,橋接模式與對象適配器模式的相似程度 他們都擁有抽象的概念角色 Abstraction和Target 它們都擁有具體的客戶端需要直接面對的角色 RefinedAbstraction和Adapter 他們都有工作需要委托給內部的“工作人員”Implementor 和 Adaptee 他們都依賴於“抽象”與“實現”的概念
那麼到底有什麼區別呢? 適配器模式的主要目的是讓因為介面不相容而不能互相工作的類能夠一起工作 換句話說就是他們本身不同,我用“紐帶” Adapter將他們連接起來 而橋接模式則是將原本或許緊密結合在一起的抽象與實現,進行分離 使她們能夠各自獨立的發展,是把連接在一起的兩個事物,拆分開來 然後用“紐帶”“橋梁”(也就是對象的引用)將他們連接起來
適配器模式就好比張三和王五不認識,李四介紹他們認識 橋梁模式好比張三和王五成天黏在一起活幹得不好太亂套,李四說以後我作為介面人,你倆各乾各的吧 雖然看起來都是兩個人,中間一個聯繫人,但是含義卻是完全不同
與裝飾器區別
裝飾器模式中,使用組合而不是繼承來對類的功能進行擴展,避免了類的個數的爆炸增長,與橋梁模式的結果不約而同 都解決了類爆炸增長的問題,都避免了過多的沒必要的子類裝飾器模式側重於功能的動態增加,將額外的功能提取到子類中 通過不同的排列組合,形成一個遞歸的調用方式,以動態的增加各部分的功能
橋梁模式是將原本系統中的實現細節抽取出來,比如原來抽象概念與實例化全部都是一個類層次結構中 把所有的實現細節,比如示例中的平臺相關實現,抽取出來,進行分離 達到抽象與實現分離的目的
所以雖然他們都可以解決子類爆炸式增長、不易擴展的問題 但是他們的出發點完全不同 一個關註於功能的動態擴展組合 一個關註於抽象與實現的分離,獲得更多的靈活性
總結
場景
如果一個系統要求抽象化角色和具體化角色之間增加更多的靈活性 避免在兩個層次之間建立靜態的聯繫 也就是實現化角色的改變,不會影響客戶端,完全透明的如果系統需要在多個抽象化角色和實現化角色之間進行動態的耦合 也就是要求能夠動態的組合
如果使用多層繼承結構進行處理時,就可以考慮使用橋接模式 尤其是一個類存在兩個或者多個變化的維度,而且這兩個維度也可能需要動態的擴展
總之,當你需要抽象化和是實現化進行解耦時,你就可以考慮橋梁模式 解耦就會變得透明不會互相影響能夠獨立發展,解耦就能夠動態的組合,解耦了就不會有靜態的聯繫
優點
提高了擴展性,多層繼承的良好替代方案 將抽象與實現進行解耦,更加符合單一職責原則 組合復用原則以及開閉原則註意點
想要使用橋接模式,必然要理清楚你面對的需求中抽象與實現的部分,才能更好的進行運用。 只要具備抽象與實現分離的相關需要,都可以考慮橋梁模式前面描述的多個維度不同發展,多層次的抽象,是橋梁模式的更高級別的運用,必須要先分析清楚變化的維度 而且最重要的就是分析出整個類層次結構中的“抽象化”部分和“實現化”部分 我們的Editor示例中,ImgEditor TextEditor才是客戶端程式關註的,他們不希望關註於具體平臺 JDBC中,客戶端程式關註的是資料庫的查詢操作行為,而不希望關註資料庫的細節 所以你必須找準到底誰是抽象化
橋接模式的場景理解起來略微有點費神 但是他的根本邏輯卻是非常簡單,那就是抽象的概念功能與具體的實現進行分離 橋接模式是面向抽象編程--依賴倒置原則的具體體現 並且使用組合的形式,解決了多層繼承結構中的一些問題 原文地址:橋接模式 橋梁模式 bridge 結構型 設計模式(十二)