1. 程式人生 > >設計模式:Bridge模式

設計模式:Bridge模式

如果你想要寫一個遊戲,並且想讓這個遊戲同時支援PC和手機,那麼怎麼樣的設計可以避免寫兩套程式碼,並且不影響可擴充套件性呢?說起來還是比較簡單的,只要把對平臺的依賴部分抽取成抽象的介面(比如說繪圖部分),並且針對抽取出來的介面,分別實現PC版和手機版就行了。系統的其他部分只要呼叫那套抽取出來的介面就可以完成所有的功能。這樣來看,系統的其他部分是不依賴具體的平臺的,也就具有了良好的擴充套件性。這個就是Bridge模式的應用。

定義
將抽象和實現相分離,使二者可以獨立的變化。(GOF)

結構圖

                                           圖一:Bridge模式的一個結構圖

例子

任何設計模式都是應用於不同的需求的,這裡我們構造了一個有關汽車的需求來應用Bridge模式。(不過,由於個人對車瞭解不多,所以有可能例子不是很恰當,不要糾結於這一點哦)

★需求
1. 概括的講,我們的需求就是實現各種各樣的汽車。從細節上講,我們知道,汽車
以用途分類,有卡車,巴士,小汽車等類別;
以動力系統分類,有汽油型,柴油型,電力型,天然氣型,生物能,油電混合型,太陽能型等等。
也就是說,有汽油型的卡車,巴士,小汽車,也有柴油型的卡車,巴士,小汽車,當然還有其他各種型別的卡車,巴士,小汽車。
這裡為了方便,我們對需求做一個簡化,只考慮卡車、巴士,汽油型和柴油型。也就是說,兩兩組合之後,就會有四種車型:汽油型卡車,柴油型卡車,汽油型巴士和柴油型巴士。

2. 任何車都有開始和停止的功能。

3. 卡車有載貨的功能,巴士有載客的功能。

★初次設計

根據以上的需求描述,想必所有人都可以設計出如圖二所示的類結構來。

                   圖二:關於汽車的最初的設計圖

★重構
雖然圖二所示的圖任何人都可以設計出來,但估計沒有人是滿意這樣的設計的。這樣的設計,不但重複程式碼多,而且要擴充套件或有所修改的話,所有的類都得大動干戈。
這裡我們就對其做一次重構。
從需求3我們知道,卡車和巴士分別有專有的功能(載貨,載客),並且這個功能是和動力系統無關的,而開車和停止的功能都是與動力系統有關的。所以我們這裡先把卡車和巴士區分出來,並且分別實現載貨和載客的功能。那麼就是如圖三所示的結構了。

                         圖三:一次重構後的設計圖

★再重構

經過圖三的重構之後,結構就清晰了些,並且程式碼的重複度也減少了一部分(載貨,載客)。但是我們還是可以看出不足之處,較明顯的地方就是,同樣的動力系統,它的開始和停止肯定是基本相似的。

其實這裡是有一個隱藏需求的。
1. 對於同一種類型的車來說,無論採用的動力系統是哪一種,其駕駛方法是相同的。也就是說,會開汽油型卡車的司機開起柴油型的卡車來也是駕輕就熟的。否則,有多少種車就得有多少種駕駛方法的話,司機沒事就得考駕照了,人手n本駕照,很恐怖的。
2. 同樣,對於同一種動力系統來說,無論安裝在哪一種車型上,恐怕主要是動力的大小是不同的,啟動和停止的方法(在軟體系統中來說應該是介面)應該是相同的。

下面我們根據以上的隱藏需求對這個設計進行再重構。既然所有的車都有動力系統,而且每個動力系統的介面都可以是一樣的,那我們就把這個動力系統提取出來作為一個單獨的類。這也是應用了Bridge模式之後的結果。具體結構圖參見圖四。


                                   圖四:應用了Bridge模式之後的重構結果

JDBC的例子
網路上看到有朋友對JDBC到底是不是應用了Bridge模式有一些分歧。這一點我們還是很明確的,JDBC確實是應用了Bridge模式。
JDBC其實只是Sun定義出來的一套規範,java.sql下的真正內容大部分都是介面。而其他的資料庫廠商則可以分別提供自己的jdbc實現來支援自家的資料庫。作為應用開發者,完全可以只靠Sun的這套JDBC介面來完成所有的應用開發,而無需關心資料庫是哪一家的。作為資料庫廠商,只需要提供自家資料庫的實現就可以了,完全不需要考慮自家的資料庫會被用來做什麼樣的應用。這也就是抽象(資料庫的應用)和實現(各廠商的JDBC實現)的分離。
再說到程式碼,想想我們用JDBC寫資料庫應用時,與具體資料庫關聯的程式碼只有一行。

這行程式碼所做的事情也非常簡單,就是建立一個自己(com.mysql.jdbc.Driver)的例項,註冊到java.sql.DriverManager中。


之後在呼叫DriverManager.getConnection()時就會從已註冊的driver中尋找合適的項並返回。

簡單的一個結構圖如圖五。

                                                     圖五:JDBC中Bridge模式的應用

在這裡,有一點小小的意外,DriverManager提供了一組靜態方法,並且私有化了自己的建構函式。可以簡單的認為只有一個例項存在(實際上資料是以類變數的形式存在)。這個看似意外的設計,導致DriverManager不再具備擴充套件性,但其實是一種更合適的設計,因為我們也不需要n多的DriverManager的例項存在,也不需要它還有任何擴充套件的可能。
JDBC的Bridge模式其實可以算是Bridge模式的另外一種表現。我們在應用設計時,也要考慮具體的場景和需求,來選用合適的結構,而不能一味的套用。
另外需要說明一下的是,圖五隻是用了兩個類來表示Bridge模式的應用。但在JDBC中,並不僅僅是Driver被抽象出來了,還有Connection,Statement等一組Interface都是被抽象出來的實現。

適用於
★類的某部分功能會劇烈變化時,把抽象和實現分離,則抽象的部分在開發時不會受到實現部分變動的影響
★想要使抽象和實現部分分別開發,互不依賴時
★想要避免抽象和實現的強耦合,使實現部分可以在執行時被動態的切換
★想要對抽象隱藏實現部分或者對實現隱藏抽象部分時(外包時可用)

心得總結
Bridge模式所應用的核心設計思想是針對介面程式設計,組合優於繼承。
從上面的汽車的設計重構過程來看,我們也可以得出這樣的結論,如果在抽取類的時候就嚴格按照CRC的原則進行的話,那麼動力系統是很容易被抽取成一個單獨的類,並且作為汽車的一個組成部分而存在。當然,Car和Engine之間只能是聚合關係,而不是組合關係,因為Engine完全可以獨立存在,或者安裝到其他的機械裝置上。

參考資料
1. 軟體設計 Bridge模式(新的理解,新的參考)
   http://humingke1984.blog.163.com/blog/static/34777159201062444653948/
2. 從橋接模式與策略模式談起
   http://www.blogjava.net/wangle/archive/2007/04/25/113545.html
3. Bridge模式學習筆記
   http://blog.csdn.net/zjibo/archive/2009/09/10/4540030.aspx
4. 再論橋接模式(上)紙上談兵
   http://blog.csdn.net/jyk/archive/2009/12/04/4936430.aspx
5. Bridge模式,Decorator模式(轉載)
   http://www.360doc.com/content/08/0801/15/63912_1497477.shtml