橋接模式Bridge
Bridge 意爲橋樑,橋接模式的做用就像橋樑同樣,用於把兩件事物鏈接起來
意圖
將抽象部分與他的實現部分進行分離,使得他們均可以獨立的發展。
意圖解析
依賴倒置原則要求程序要依賴於抽象接口,不要依賴於具體實現。
簡單的說就是要求對抽象進行編程,不要對實現進行編程,這樣就下降了客戶與實現模塊間的耦合
抽象
抽象就是將多個事物、實體中共同的概念提取出來
好比,一組共同特性的對象概念,能夠提取出來類
若是一些類又具備共同的概念性聯繫,又能夠提取出來抽象類和接口
實現
抽象的具體,就是實現
好比一個對象是一個類的實現,一個具體的子類是抽象父類的實現
類的功能層次結構
按照依賴倒置原則,咱們面向抽象進行編程
一般會使用接口或者抽象類用於描述功能概念
而後經過繼承進行功能概念的擴展,子類繼承父類,而且擴展父類以增長新的功能
類的實現層次結構
在基於功能層次結構的基礎上,須要對方法接口或者概念等進行具體實現
這些全部的實現就組成了類的實現層次結構
好比:
定義一個圖片編輯器imageEditor(接口)
分爲windows和Linux兩個平臺(接口)
而後又分別有兩個實現類windowsImpl 以及LinuxImpl(實現類)
圖中,紅框部分即爲類的功能層次結構,藍色矩形框部分即爲類的實現層次結構
對於類的層級結構不是很複雜的時候,咱們可能常常會將類的功能層次結構和實現層次結構摻雜在一塊兒
好比,你可能定義了一個抽象類A,而後他有兩個子類B和C,B是用於實現,另外一個C倒是功能邏輯的擴展
當層次接口相對比較簡單的時候,摻雜在一塊兒,還相對容易應對
當層次結構變得很複雜時,若是仍是摻雜在一塊兒將會變得很是難以處理
由於當你想要擴展產品功能邏輯時,你可能很難肯定到底應該在類的哪個層次結構中去擴展
咱們將編輯器抽象提取出來Editor(接口)
又有圖形編輯器ImageEditor(接口)文本編輯器TextEditor(接口)視頻編輯器VideoEditor(接口)
又分別有windows和linux兩個版本的軟件
紅色框內爲功能層次結構,藍色框內爲實現層次接口
上圖就是經過繼承的形式進行功能擴展與實現
能夠看得出來,若是新增長一種新的編輯器,好比音頻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類型以及不一樣平臺實現兩個維度
也正是這兩個維度致使了不符合單一職責原則
是否能夠將這兩個維度進行分離?
若是可以分離,也就意味着不會是徹底使用繼承層次結構
由於繼承是強關聯,徹底繼承,就不是分離
那麼,就須要考慮類的組合
|
若是可以分離,將他們拆分爲各自不一樣的維度,那麼就不會出現類的個數爆炸式增加的狀況
由於一旦分離,就能夠各自獨立發展
獨立發展,那麼增長一個Editor類型就是一個Editor類型
增長一個操做系統平臺,就只是增長一個操做系統平臺類型
不在爆炸增加
|
若是可以進行分離,那麼他們各自負責本身不一樣的維度,職責將會更加單一 |
客戶端關注什麼?
客戶端程序的目的在於使用Editor,也就是Img、Text、Video的編輯
他其實並不在乎究竟是什麼平臺
並且,他也不該該在乎,只有這樣纔可以跨平臺
可是,在咱們的類層次結構中,卻恰恰的與平臺創建了強關聯
因此一種解決方案就是:
客戶端面向Editor進行編程
Editor不關注平臺的細節,將與平臺相關的實現剝離開來
而平臺的實現部分,經過組合的方式,組合到Editor中來
|
相似適配器模式(對象適配器模式),Editor做爲目標對象Target
而與平臺相關聯的實現就是被適配的角色Adaptee
而每個類型好比ImgEditor都是Adapter
關於平臺相關的細節部分,經過組合的方式
以下圖所示Editor中含有指向Implementor的引用
涉及平臺相關性的處理,藉助於Implementor來完成
代碼示例
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();
}
}
以上代碼爲上圖中的左半部分
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");
}
}
測試代碼
package bridge;
public class Test {
public static void main(String[] args) {
Editor editor = new ImgEditor();
editor.setImplementor(new WindowsImpl());
editor.doEdit();
}
}
在示例代碼中,建立了一個ImgEditor
而後動態的藉助於new WindowsImpl() 在windows平臺上執行編輯任務
在真正的藉助於平臺底層,執行平臺相關的代碼以前,還進行了一些其餘的處理
好比上面的打印語句:
System.out.println("ImgEditor working");
這種使用方式,用戶關注的是Editor,不在與具體的平臺進行綁定
具體的平臺經過組合的形式組合到Editor中
在具體的使用到平臺相關的方法中,Editor依賴於內部的Implementor 進行處理
擴展時,也不會出現類文件個數爆炸增加的問題
能夠相互獨立發展,這就是橋接模式
結構
抽象化角色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的百度百科
JDBC是橋樑模式的典型應用
他將抽象化與實現化進行分離
它爲全部的關係數據庫提供了一個通用的訪問界面
提供了一組與具體廠家實現徹底無關的接口
有了JDBC這一組通用接口
應用系統就能夠不依賴數據庫引擎的具體細節而獨立的演化
一個應用系統動態的選擇一個合適的驅動器,而後經過驅動器向數據庫引擎發出指令
這就是抽象角色的行爲委派給實現角色來完成任務
廠家的實現與JDBC之間,並無任何的靜態強關聯
其實不少其餘形式的驅動又未嘗不是如此?
好比Office辦公軟件打印不須要關注於具體的打印機廠家型號
會有統一的驅動爲咱們進行處理
模式對比
與適配器區別
上面說到橋接模式相似適配器模式
並且,某種程度上講,適配器模式也符合
「抽象化---實現化」的概念
並且,從上面的結構圖中,能夠看得出來,橋接模式與對象適配器模式的類似程度
他們都擁有抽象的概念角色 Abstraction和Target
它們都擁有具體的客戶端須要直接面對的角色 RefinedAbstraction和Adapter
他們都有工做須要委託給內部的「
工做人員
」Implementor 和 Adaptee
他們都依賴於「抽象」與「實現」的概念
那麼到底有什麼區別呢?
適配器模式的主要目的是讓由於接口不兼容而不能互相工做的類可以一塊兒工做
換句話說就是他們自己不一樣,我用「紐帶」 Adapter將他們鏈接起來
而橋接模式則是將本來或許緊密結合在一塊兒的抽象與實現,進行分離
使她們可以各自獨立的發展,是把鏈接在一塊兒的兩個事物,拆分開來
而後用「紐帶」「橋樑」(也就是對象的引用)將他們鏈接起來
適配器模式就比如
張三和王五不認識,李四介紹他們認識
橋樑模式比如張三和王五整天黏在一塊兒活幹得很差太亂套,李四說之後我做爲接口人,你倆各幹各的吧
雖然看起來都是兩我的,中間一個聯繫人,可是含義倒是徹底不一樣
與裝飾器區別
裝飾器模式中,使用組合而不是繼承來對類的功能進行擴展,避免了類的個數的爆炸增加,與橋樑模式的結果不約而同
都解決了類爆炸增加的問題,都避免了過多的不必的子類
裝飾器模式側重於功能的動態增長,將額外的功能提取到子類中
經過不一樣的排列組合,造成一個遞歸的調用方式,以動態的增長各部分的功能
橋樑模式是將本來系統中的實現細節抽取出來,好比原來抽象概念與實例化所有都是一個類層次結構中
把全部的實現細節,好比示例中的平臺相關實現,抽取出來,進行分離
達到抽象與實現分離的目的
因此雖然他們均可以解決子類爆炸式增加、不易擴展的問題
可是他們的出發點徹底不一樣
一個關注於功能的動態擴展組合
一個關注於抽象與實現的分離,得到更多的靈活性
總結
場景
若是一個系統要求抽象化角色和具體化角色之間增長更多的靈活性
避免在兩個層次之間創建靜態的聯繫
也就是實現化角色的改變,不會影響客戶端,徹底透明的
若是系統須要在多個抽象化角色和實現化角色之間進行動態的耦合
也就是要求可以動態的組合
若是使用多層繼承結構進行處理時,就能夠考慮使用橋接模式
尤爲是一個類存在兩個或者多個變化的維度,並且這兩個維度也可能須要動態的擴展
總之,當你須要抽象化和是實現化進行解耦時,你就能夠考慮橋樑模式
解耦就會變得透明不會互相影響可以獨立發展,解耦就可以動態的組合,解耦了就不會有靜態的聯繫
優勢
提升了擴展性,多層繼承的良好替代方案
將抽象與實現進行解耦,更加符合單一職責原則 組合複用原則以及開閉原則
注意點
想要使用橋接模式,必然要理清楚你面對的需求中抽象與實現的部分,才能更好的進行運用。
只要具有抽象與實現分離的相關須要,均可以考慮橋樑模式
前面描述的多個維度不一樣發展,多層次的抽象,是橋樑模式的更高級別的運用,
必需要先分析清楚變化的維度
並且最重要的就是分析出整個類層次結構中的「抽象化」部分和「實現化」部分
咱們的Editor示例中,ImgEditor TextEditor纔是客戶端程序關注的,他們不但願關注於具體平臺
JDBC中,客戶端程序關注的是數據庫的查詢操做行爲,而不但願關注數據庫的細節
因此你必須找準到底誰是抽象化
橋接模式的場景理解起來略微有點費神
可是他的根本邏輯倒是很是簡單,那就是抽象的概念功能與具體的實現進行分離
橋接模式是面向抽象編程--依賴倒置原則的具體體現
而且使用組合的形式,解決了多層繼承結構中的一些問題