轉載自:http://www.cnblogs.com/wukenaihe/archive/2013/04/17/3026429.html
1 裝飾模式概念
1.1 裝飾者模式定義
定義:裝飾模式的基本含義是能夠動態地爲一個對象添加一些額外的行爲職責。
談到對象行爲職責的擴展,我們很容易就能夠想到面向對象編程語言的一個重要特徵:集成。繼承是絕大多數面向對象的編程語言在語法上的天然支持。通過使用繼承,我們可以獲取以下兩種擴展特性:
- 現有對象行爲的覆蓋——通過覆寫(Override)父類中的已有方法完成。
- 添加新的行爲職責——通過在子類中添加新的方法完成。
但是繼承這個語法,爲對象類型引入的是一種“靜態”特性擴展。這一擴展在行爲特性的獲取在“編譯期”就被決定了,而並非是一個“運行期”的擴展模式。隨着子類的增多,雖然我們獲得了更多的擴展功能,然而各種子類的組合(擴展功能的組合)將導致子類的極度膨脹。在java語言中,一個類智能進行“單根繼承”而無法支持“多重繼承”,因而通過“繼承”這種方式進行功能行爲特性的擴展缺乏足夠的靈活性。後面,通過例子來詳細說明下這個的嚴重性。
裝飾者模式體現了軟件設計一個非常重要的設計原則:類應該對擴展開放,對修改關閉。
1.2 裝飾者模式組成
圖1 裝飾模式的一種基本實現示意圖
從圖1中我們可以歸納出裝飾模式的基本實現中所涉及的角色,用人穿衣服類比喻吧:
原始接口(Component)——定義了一個接口方法。用來規範的。
模式目標實現類(ConcreteComponent)——對於原始接口的默認實現方式。在裝飾模式中,默認目標實現類被認爲是有待擴展的類,其方法operation被認爲是有待擴展的行爲方法。就是要被裝飾的類,就是人。
裝飾實現類(Decorator)——同樣實現了原始接口,既可以是一個抽象類,也可以是一個具體實現類。其內部封裝了一個原始接口的對象實例:ConcreteComponent,這個實例的實現往往被初始化成默認目標實現類(ConcreteComponent)。衣服這個概念!
具體裝飾實現類(ConcreteDecorator)——繼承自裝飾實現類(ComponentDecorator),我們可以再operation方法中調用原始接口的對象實例ConcreteComponent獲得默認目標實現類的行爲方式並在其中加入行爲擴展實現,也可以添加自由添加新的行爲職責addBehaviorA等。自然是內衣之類的!
1.3 應用場景
裝飾者模式以對客戶端透明的方式擴展對象功能,是集成的一種替代方案。就是我們動態的給一個對象附加功能時,客戶端是感覺不出來的,代碼完全不會有任何改變。
裝飾者模式可以在不創造更多子類的情況下,將對象的功能擴展。
裝飾模式具備了一些比“繼承”更加靈活的應用場景:
- 適合對默認目標實現中的多個接口進行排列組合調度
- 適合對默認目標實現進行選擇性擴展(java.io.InputStream)
- 適合對默認目標實現未知或者不易擴展的情況(HttpServletResponseWrapper)。
2 JAVA類庫中的策略模式
2.1 Java.io.Inputstream
2.1.1 日常使用中的裝飾者模式
Java.io一個簡單的例子
1 public static void main(String[] args) throws IOException { 2 DataOutputStream dos = new DataOutputStream(new BufferedOutputStream( 3 new FileOutputStream("data.txt")));//關鍵點 4 5 byte d = 3; 6 int i = 14; 7 char ch = 'c'; 8 float f = 3.3f; 9 10 dos.writeByte(d); 11 dos.writeInt(i); 12 dos.writeChar(ch); 13 dos.writeFloat(f); 14 15 dos.close(); 16 17 DataInputStream dis = new DataInputStream(new BufferedInputStream( 18 new FileInputStream("data.txt")));//關鍵點 19 20 System.out.println(dis.readByte()); 21 System.out.println(dis.readInt()); 22 System.out.println(dis.readChar()); 23 System.out.println(dis.readFloat()); 24 25 dis.close(); 26 27 }
結果:
圖2 讀取結果和二進制文件
在關鍵點,FileIutputStream("data.txt")這個就是默認的目標實現類。FileIutPutStream是最基本的吧,讀取文件的一個類。然後對它進行了兩成包裝,第一層BufferedIutputStream,這個包裝後輸入類就有緩存的功能;第二層DataIutputStream,這個包裝後輸出類就有了數據類型的功能。在不知不覺中,我們一直用着裝飾者模式。
在java.io.inputstream的裝飾者模式如下所示:
圖3 InputStream的裝飾者模式示意圖(部分)
我們在圖3中展示了部分的InputStream,FileInputStream、ByteArrayInputStream、StringBufferInputStream是屬於目標實現類,分別從文件系統中獲取字節流、從流中讀取字節、以字符串的方式獲取字節流。默認目標實現類在這裏是源,是最基本的東西。實現FilterInputStream的幾個類,便是具體裝飾實現類,分別可以添加緩存、校驗、數據類型、跟蹤行號的功能。
所以使用java.io的時候,我們只要記住這種模式就好:
new ConcreteDecoratorA(new ConcreteDecoratorB(new ConcreteComponent));
包裝幾層我不管,只要最裏面的是一個默認目標實現類就可以了,這樣java.io的用法是否就簡單易記了啊!要不然,這麼多類還不搞死人。這裏我們是不是就體現了裝飾者模式的第二種使用場景,適合對默認目標實現進行選擇性擴展,那些附加功能你愛怎麼選怎麼選。
2.1.2 InputStream源碼分析
1.原始接口(InputStream)
1 public abstract class InputStream implements Closeable { 2 public abstract int read() throws IOException; 3 //read(byte b[])、read(byte b[], int off, int len)調用的還是read() 4 }
這是一個抽象類,在這個抽象類中只有一個抽象方法read(),也就是一定要用子類去實現的。不過別的函數會調用這個方法。所以,InputStream的所有子類都必須實現read()。
2.默認目標實現類(FileInputStream)
1 class FileInputStream extends InputStream 2 { 3 public native int read() throws IOException; 4 }
這個默認目標實現類裏面,實現了父類InputStream中的抽象方法,當然用的是native本地方法我是看不到的。不過,肯定是read()方法已經實現了的。
3. 裝飾實現類(FilterInputStream)
1 class FilterInputStream extends InputStream { 2 protected volatile InputStream in; 3 /*裝飾者模式裏面最重要的特徵1,默認目標實現類封裝於裝飾實現類或者其子類的內部,從而形成對象之間的引用關係。 4 簡單的說,裝飾實現類(FilterInputStream)一定是繼承或實現原始接口(InputStream)的,內部有包含一個原始接口的超類(其實就是某個默認目標實現類)。 5 */ 6 protected FilterInputStream(InputStream in) { 7 this.in = in; 8 } 9 /*一定會提供一個口子,來初始化目標這個超類(in)。說穿了,裝飾實現類(衣服),沒有這個目標實現類(人)我什麼都幹不了,具體是哪個人跟我衣服就沒關係了,你穿給我就好*/ 10 }
裝飾實現類,是整個裝飾者模式裏面的核心,應該說是一看就看出是裝飾者模式的地方。兩個重要特徵:
- 默認目標實現類封裝與裝飾實現類(ComponentDecorator)或者其子類的內部,從而形成對象之間的引用關係
- 裝飾實現類(ComponentDecorator)同樣實現了原始接口(Component)
4.具體裝飾實現類(BufferedInputStream、DataInputStream)
BufferedInputStream源碼
1 public class BufferedInputStream extends FilterInputStream { 2 public BufferedInputStream(InputStream in) { 3 this(in, defaultBufferSize); 4 } 5 6 public BufferedInputStream(InputStream in, int size) { 7 super(in);//裝飾者類裏面現在又具體的目標實現類了 8 if (size <= 0) { 9 throw new IllegalArgumentException("Buffer size <= 0"); 10 } 11 buf = new byte[size]; 12 } 13 //返回的就是剛剛傳進去的目標實現類FileOutputStream("data.txt") 14 private InputStream getInIfOpen() throws IOException { 15 InputStream input = in; 16 if (input == null) 17 throw new IOException("Stream closed"); 18 return input; 19 } 20 private void fill() throws IOException { 21 byte[] buffer = getBufIfOpen(); 22 //這裏本來還有n多內容,處理緩存大小,位置之類的 23 int n = getInIfOpen().read(buffer, pos, buffer.length - pos); 24 //上面這句非常重要,getInIfOpen()返回的是in(其實就是FileOutputStream("data.txt")),然後調用了目標實現類裏面的方法,放到了buffer這個緩存中。 25 if (n > 0) 26 count = n + pos; 27 } 28 public synchronized int read() throws IOException { 29 if (pos >= count) { 30 fill(); 31 if (pos >= count) 32 return -1; 33 } 34 return getBufIfOpen()[pos++] & 0xff; 35 }
DataInputStream源碼
1 public class DataInputStream extends FilterInputStream implements DataInput { 2 public DataInputStream(InputStream in) { 3 super(in); 4 } 5 //int,四個4字節,每次讀取一個字節,只要把這四個字節拼裝成int的順序,就成爲一個int了。 6 public final int readInt() throws IOException { 7 int ch1 = in.read();//每次讀一個字節 8 int ch2 = in.read(); 9 int ch3 = in.read(); 10 int ch4 = in.read(); 11 if ((ch1 | ch2 | ch3 | ch4) < 0) 12 throw new EOFException(); 13 return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)) 14 }
現在讓我們來回顧下,我們的例子中dis.readInt(),調用了DataInputStream裏面的readInt(),in.read()調用了BufferInputStream中的read,然後BufferInputStream裏面又調用了FileInputStream裏面的本地方法read()。這樣就完成了僅僅讀取一個字節,到把讀取過的緩存起來,再到客戶端看起來是讀取了一個int,這樣華麗的轉變。
在這個應用裏面,我們調用的順序是有一定要求的。就緩存這個東西爲例吧!如果我們用的是繼承的方式,那麼帶類型的輸入,就至少有兩個子類DataInputStream和BufferDataInputStream。然後,別的LineNumberInputStream,這種類是不是也要緩存不緩存的分。如果,還有別的功能呢,帶檢查不帶檢查,這樣組合的話,子類是不是會急劇膨脹。
OutputStream和InputStream沒有本質上的區別,所以這裏就不講了。
2.2 HttpServletRequestWrapper
2.2.1 Servlet編程中的裝飾者模式
SampleRequsetWrapper類
1 public class SampleRequestWrapper extends HttpServletRequestWrapper{ 2 3 public SampleRequestWrapper(HttpServletRequest request) { 4 super(request); 5 } 6 7 public String getParameter(String s){ 8 System.out.println("here is the decorator!"+s); 9 return super.getParameter(s); 10 } 11 }
SampleFilter類
1 public class SampleFilter implements Filter { 2 3 private FilterConfig filterConfig; 4 @Override 5 public void doFilter(ServletRequest request, ServletResponse response, 6 FilterChain chain) throws IOException, ServletException { 7 HttpServletRequest req = (HttpServletRequest) request; 8 req = new SampleRequestWrapper(req); 9 chain.doFilter(req, response); 10 } 11 }
web.xml
<filter> <filter-name>sampleFilter</filter-name> <filter-class>SampleFilter</filter-class> </filter> <filter-mapping> <filter-name>SampleFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
每次使用request. getParameter()時,都會在控制面板裏面輸出上述結果。這裏是簡單的輸出結果,你也可以加入別的功能如“優先從緩存中讀取數據”,“編碼設置”等功能。
在HttpServletRequestWrapper的裝飾者模式設計框架如圖所示:
圖4 HttpServletRequestWrapper裝飾者模式示意圖
我們知道,HttpServletRequset和HttpServletResponse是Servlet標準所指定的Java語言與Web容器進行交互的接口。接口本身只規定java語言對web容器進行訪問的行爲方式,而具體的實現是由不同的web容器在其內部實現的(上圖request就是tomcat的實現)。
那麼在運行期,當我們需要對HttpServletRequset和HttpServletResponse的默認實例進行擴展時,我們就可以繼承HttpServletRequestWrapper和HttpServletResponseWrapper來實現。
2.2.2 HttpServletRequestWrapper源碼分析
HttpServletRequestWrapper源碼
1 public class HttpServletRequestWrapper extends ServletRequestWrapper implements HttpServletRequest { 2 public HttpServletRequestWrapper(HttpServletRequest request) { 3 super(request); 4 } 5 }
HttpServletRequestWrapper是一個什麼都沒做的具體裝飾實現類(concerteDecorator),不過它即繼承了裝飾實現類,又實現了HttpservletRequst。這裏什麼都不做是有道理的,因爲要對請求添加何種功能事先是沒法知道的,不可能盲目的添加。所以,這種裏的裝飾模式的應用場景:適合對默認目標實現未知或者不易擴展的情況。
ServletRequestWrapper源碼
1 public class ServletRequestWrapper implements ServletRequest { 2 private ServletRequest request; 3 4 public ServletRequestWrapper(ServletRequest request) { 5 if (request == null) { 6 throw new IllegalArgumentException("Request cannot be null"); 7 } 8 this.request = request; 9 }
這個類很明顯就是裝飾實現類(Decorator),提供了一個可以ServletRequest(Component)的構造函數,並在構造函數中實現了將原始ServletRequset接口的實現封裝於內部的基本邏輯。這樣一來,構成裝飾模式的兩大基本要素也就全齊了。
我們進一步思考,還能挖掘出裝飾者模式作爲對象行爲的擴展方式比繼承更爲合理的地方:雖然裝飾模式產生的初衷是裝飾類對默認目標實現類的行爲擴展,然後卻並不對默認目標實現類形成依賴。
由於在裝飾類內部封裝的是接口而並不是默認目標實現類,這樣一來,我們在實現目標時,甚至無須知道具體的實現類是誰。如果,上面的例子用繼承方式來實現,你就不得不知道使用的是哪種web容器,並且要知道具體會是哪個類實現了HttpServletRequest或HttpServletResponse。
3 總結
裝飾者模式應用場景
- 適合對默認目標實現中的多個接口進行排列組合調度
- 適合對默認目標實現進行選擇性擴展
- 適合對默認目標實現未知或者不易擴展的情況。
裝飾者模式優點
- 通過組合而非繼承的方式,實現了動態擴展對象的功能的能力。
- 有效避免了使用繼承的方式擴展對象功能而帶來的靈活性差,子類無限制擴張的問題。
- 充分利用了繼承和組合的長處和短處,在靈活性和擴展性之間找到完美的平衡點。
- 裝飾者和被裝飾者之間雖然都是同一類型,但是它們彼此是完全獨立並可以各自獨立任意改變的。
- 遵守大部分GRASP原則和常用設計原則,高內聚、低偶合。
裝飾者模式缺點
- 性能影響,這麼多的多態和調用,肯定好不了。
- 裝飾者會導致設計中出現許多小對象,如果過度使用,會讓程序變的很複雜。
4 參考資料
[1]. 《Head First設計模式》
[2]. 《Struts2技術內幕》