对Bridge模式的理解
<轉>對Bridge模式的理解
對Bridge模式的理解
摘要:本文首先解釋了Bridge模式的定義。然后通過一個例子,一步步將Bridge模式實現。
?
在一切開始之前,請允許我先給出三條經典名言:Design to interfaces. Find what varies and encapsulate it. Favor composition over inheritance.后面我們會反復,并且是反反復復的用到。我認為在做設計的時候這三句話要牢牢的印在腦子里。
一.? 定義
根據GOF的定義,Bridge模式的目的是“解耦抽象與它的實現,以便二者可以獨立的變化。”這個定義中最容易誤解的“抽象”與“它的實現”。因為這兩個詞在面向對象的語言中都有對應的關鍵字。在Java中即是“abstract”和“implement”,所以很容易造成困惑的是認為要解耦一個抽象類和它的實現類。實際上,這里實現是指的抽象類和它的派生類用以實現自己的對象,進一步說就是這里的抽象指的是一個概念或者說是一個繼承體系中的對象,而實現被抽象使用并完成自己的功能。舉個例子,這是呂震宇兄想出的一個例子(http://www.cnblogs.com/zhenyulu/articles/67016.html),這是我見過的最經典的例子了,以至于我不得不在這里重復一遍:
小時候我們都用蠟筆畫畫,一盒蠟筆12種顏色。一開始我都是用最小號的蠟筆畫個太陽公公、月亮婆婆足夠了。后來開始畫一些抽象派的作品,就得換中號的了,要不然畫個背景都要描半天,好一盒中號的也是12種顏色。再后來我開始轉向豪放派,中號就有些捉襟見肘了,只好換大號的了,好一盒大號的也只有12種顏色。你看,像我這樣不太出名的畫家就需要36種畫筆,哇,太麻煩了。但是據我觀察,另一些比我出名的畫家倒是沒有這么多筆,他們只有幾把刷子和一些顏料,這樣就解決了蠟筆的“種類爆炸”問題。如下圖所示,注意圖也是從呂兄的網站偷來的。
我要用36種蠟筆
齊白石老先生只用3種毛筆和12種顏料
回到上面的定義,定義里面的抽象指的就是毛筆,實現就是顏料。顏料被毛筆使用,用以完成毛筆的功能。
(http://www.niufish.com/books/Pattern/com/niufish/pattern/bridge/package-use.html)
在下認為上圖中有以下幾點可以變通:
1.?? Abstraction與Implementor的聚合關系。該關系可以由RefinedAbstraction和Implementor的聚合關系代替。理由是Java編程的時候Abstraction可能會實現為interface,不能與Implementor構成聚合關系。
2.?? Operation方法不一定在Abstraction中做實現,理由同上,但是必須聲明。
可見在Bridge模式中有兩個繼承體系,為了方便描述我們稱左邊的為Abstraction繼承體系,右邊為Implementor繼承體系。Abstraction使用Implementor完成自己的功能。同時,該模式允許Abstraction和Implementor各自獨立變化(所謂變化,我認為就是派生)。
二.? 解決的問題
上面已經說了,提出毛筆的概念目的是解決蠟筆的“種類爆炸”問題。3×12變成了3+12,并且在將來毛筆型號和顏料種類可以獨立的擴充。上面的例子在說明這種設計模式的特點和優勢上很有好處,但是畢竟我們實際編碼中很少這么幸運的碰上這么簡單的問題。下面我用一個相對復雜的問題來重新描述這個模式,關于蠟筆和毛筆的故事的代碼可以到上面給出的鏈接查找。
三.? 一個更加復雜的例子
這里我們給出一個更為復雜的例子,并且用一種循序漸進的方式描述,逐漸加入新的功能和約束條件。設想我們要做一個編輯器(Editor),可以打開文本文件,但是不同的文件要求用不同的編輯器打開。比如“.txt”文件用文本編輯器打開,而“.xml”文件用xml編輯器打開。
代碼如下:
Editor接口
package com.gemplus.editor;public abstract interface Editor {
??? public void openFile(String path);
?
}
TextEditor實現
package com.gemplus.editor;?
public class TextEditor implements Editor { ??? public void openFile(String path) { ??????? System.out.println("Open file with Text Editor. FileName: " + path); ??? } }XMLEditor實現
package com.gemplus.editor;?
public class XMLEditor implements Editor {?
??? public void openFile(String path) { ??????? System.out.println("Open file with XML Editor. FileName: " + path); ??? } }?Client
package com.gemplus.editor;?
public class Client { ??? public static void main(String[] args) { ??????? Editor xmlEditor = new XMLEditor(); ??????? xmlEditor.openFile("test.xml");?
??????? Editor textEditor = new TextEditor(); ??????? textEditor.openFile("test.txt"); ??? } }?
輸出
Open file with XMLEditor. FileName: test.xml
Open file with Text Editor. FileName: test.txt
到目前為止,我們什么模式也沒用到。不過我們還是用到了一項偉大的技術多態,還有就是對接口編程。代碼寫的還算優雅,只是沒有寫注釋。
接下來我們要增加一個功能,與其說是功能,不如說是一項約束。就是,我不希望客戶端了解那些文件要由xmlEditor打開,哪些由textEditor打開。但是總要有人知道,對吧?這項看似直觀的約束往往被忽略,或者在潛意識中沒有意識到。為了簡單起見,我們引入一個簡單工廠EditorImpl。
?
public class SmartEditor implements Editor {?
??? private static Editor textEditor = new TextEditor(); ??? private static Editor xmlEditor = new XMLEditor(); ??? ????public void openFile(String path) { ????? ??if (path.endsWith(".xml")) { ??????????? xmlEditor.openFile(path); ??????? } else { ??????????? textEditor.openFile(path); ??????? } ??? } }Client也要做相應修改
public class Client { ??? public static void main(String[] args) { //??????? Editor xmlEditor = new XMLEditor(); //??????? xmlEditor.openFile("FileName"); //??????? Editor textEditor = new TextEditor(); //??????? textEditor.openFile("FileName2"); ??????? Editor editor = new SmartEditor(); ??????? editor.openFile("test.xml"); ??????? editor.openFile("test.txt"); ??? } }輸出結果與修改之前完全一樣,但是現在Client端已經不需要知道應該使用哪個Editor了,這其實也是對Find what vary and encapsulate it的應用。另外一點,也可以請大家注意在EditorImpl的實現中,即繼承了Editor接口又使用了組合這其實是聯合inheritance和composition的優點。Javaworld上有一篇文章(作者Bill Venners)就是講這個技巧和使用方法的,我找了半天沒找到,以后找到再把鏈接添上。
先別高興太早,我們看看目前這種設計有什么問題。SmartEditor在充滿了技巧和高級的、偉大的技術的同時,你有沒有覺得它管的東西太多了呢?一個Editor要去關心文件名的問題,不錯SmartEditor出現的目的就是要來關心文件名,然而從概念上講,仍然不是好的設計。我們來看看什么在變化?答案是文件類型。OK,封裝之!
我們把它稱作什么呢?對應與Edtior,我們不妨稱之為Editable。(說實話,我這樣這個概念建立起來有一些生硬,但是你可以想像一下:如果被編輯的東西是更加復雜的呢?我所取的例子實際上是我工作中的一個例子,要編輯的東西要比這里描述的復雜的多!)
簡單之極,以至于我覺得沒有必要給出代碼。但是第一次寫文章,總要給大家留點好印象。
??? String filePath;
?
??? public FileEditable(Editor editor, String filePath) { ??????? this.editor = editor; ??????? this.filePath = filePath; ??? }?
??? public void open() { ??????? editor.openFile(filePath); ??? }?
}Client端
public class Client { ??? public static void main(String[] args) {?
??????? Editable editableText = new FileEditable(new TextEditor(), "test.txt"); ??????? Editable editableXML = new FileEditable(new XMLEditor(), "test.xml"); ??????? editableText.open(); ??????? editableXML.open(); ??? } } FileEditableEditor ??? public FileEditable(String filePath) { ??????? this.filePath = filePath; ??????? if (filePath.endsWith(".xml")) { ??????????? editor = new XMLEditor(); ??????? } else { ??????????? editor = new TextEditor(); ??????? } ??? }現在該是增加功能的時候了。現在我們要求,用戶不但可以打開文件也有可能用這個Editor來編輯一段文字,即字符串。我們假設,所有的Editor都可以編輯文件和字符串。我們為Editor增加一個方法:openString。前面我們說了,Bridge模式中有兩個繼承體系,兩邊可以獨立變化。現在我們有了兩個Editable,即兩種可編輯體:文件和字符串。也可以看出我們當初把Editable封裝起來多么的英明神武啊!!
在Editable的繼承體系中又增加了一員:StringEditable。
雖然我前面已經說了,Abstraction和Implementor的聚合關系可以由派生類RefinedAbstraction和Implementor的聚合關系代替,為了與經典模式類圖保持盡量的相似,以便大家容易理解,我們還是在StringEditable和FileEditable上面加了一層:EditableImpl。
好了,我們已經實現了Bridge模式。不過這里面還是有一點讓人不舒服的地方,就是Editor中包含的兩個方法,實際上對應了兩種Editable。也許是這個例子舉的不好。但是《設計模式 explained》中的例子和本例大同小異。
我也是剛剛開始學習設計模式,如果有什么不對的地方請大家指出。接下來,我會繼續這個例子,增加一個更加復雜的功能:可以嵌套組合的Editor。比如對于同一個文件我們可能希望有兩種打開方式,以網頁為例,我們希望在一個Editor中包含兩個子Editor,分別顯式源代碼和頁面效果。這里面會用到Composite模式。
參考資料:
1.?? http://www.cnblogs.com/zhenyulu/articles/67016.html
《設計模式隨筆-蠟筆與毛筆的故事》本文對于Bridge模式給了一個相當漂亮的比喻,并且給出了代碼示例。
2.?? http://www.cnblogs.com/zhenyulu/articles/62720.html
《設計模式(16)-Bridge Pattern》這篇文章沒有仔細看,前面的角色定義和解釋很精辟。
3.?? http://www.cnblogs.com/idior/articles/97283.html
《Bridge Strategy 和State的區別》個人認為,作者在Bridge和Strategy模式的區別上面有誤解。下面有我的回復:
我倒是不贊同樓主所說在“蠟筆與毛筆的故事”中“缺少被抽象的行為”的說法。Bridge模式中解耦的是“抽象”與“實現”。這里的實現不一定是對“行為”的抽象,按照《Design Pattern Explained》 一書中所說,這里實現是指的抽象類和它的派生類用以實現自己的某個對象(本身是抽象與其派生類構成的繼承體系,要不怎么變化呢)。進一步說就是這里的抽象 指的是一個概念或者說是一個繼承體系中的對象,而實現則是被抽象使用并完成自己的功能的另一個繼承體系。橋梁模式的目的就是讓這兩個繼承體系可以,獨立的 變化、派生。蠟筆與毛筆的故事中毛筆(第一個繼承體系)和顏料(第二個繼承體系)可以獨立的變化,所以我認為這是個非常恰當的橋梁模式的例子。
繼續:)
我覺得區分兩個模式的方法不是從模式的實現上面看,因為一個模式的實現往往夾雜了其它的模式,比如idior給的第一個例子中就有Template Method模式。我贊成呂震宇的說法,這里面有Bridge模式的影子,甚至我覺得不止是影子,這本身是一個Bridge模式的例子。
區分兩個模式的方法應該從解決的問題上看,也就是從context上分析。
我覺得簡單的說Strategy模式是從N變化為1+N,原來有N個類但是這N個類里面只有某個算法的區別,我們把N個算法提取出來就變成了1個抽象類(不要理解成Java中的abstract class,而是這個抽象類表示一個概念)和N個實現類(同理,不要理解成對前面那個抽象類的實現,而是輔助實現抽象類的某個功能的一個繼承體系)。注意這里只有一個繼承體系。
而Bridge模式是從M×N變化為M+N,原來系統中有M×N個類,但是從中可以提取出N個算法(或者輔助類)和M個主體(我想不出一個好的名次)。這樣構成了兩個繼承體系,N個算法(顏料)構成一個繼承體系,M個主體類(毛筆的不同型號)構成一個繼承體系。兩個繼承體系可以獨立的變化。
從解決的問題上看,二者都要解決重復代碼的問題,但是前者不強調錐把(見呂震宇的回復)的變化,而后者強調,并且強調錐頭和所有錐把的兼容。我認為這才是二者的根本區別。
這是我個人的理解,與樓主商榷。
4.??? http://www.niufish.com/books/Pattern/com/niufish/pattern/bridge/package-use.html
設計模式速查,很不錯總結
以上是生活随笔為你收集整理的对Bridge模式的理解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在Redhat中添加微软雅黑字体
- 下一篇: 个人电脑详细的安全设置方法之一