(十)裝飾器模式詳解(與IO不解的情緣)

LZ到目前已經寫了九個設計模式,回過去看看,貌似寫的有點凌亂,LZ後面會盡可能改進。java

                 那麼本章LZ和各位讀友討論一個與JAVA中IO有着不解情緣的設計模式,裝飾器模式。web

                 定義:裝飾模式是在沒必要改變原類文件和使用繼承的狀況下,動態的擴展一個對象的功能。它是經過建立一個包裝對象,也就是裝飾來包裹真實的對象。算法

                 這一個解釋,引自百度百科,咱們注意其中的幾點。設計模式

                 1,不改變原類文件。數組

                 2,不使用繼承。app

                 3,動態擴展。eclipse

                 上述三句話一語道出了裝飾器模式的特色,下面LZ給出裝飾器模式的類圖,先上圖再解釋。oop


                  從圖中能夠看到,咱們裝飾的是一個接口的任何實現類,而這些實現類也包括了裝飾器自己,裝飾器自己也能夠再被裝飾。測試

                  另外,這個類圖只是裝飾器模式的完整結構,但其實裏面有不少能夠變化的地方,LZ給出以下兩條。this

                  1,Component接口能夠是接口也能夠是抽象類,甚至是一個普通的父類(這個強烈不推薦,普通的類做爲繼承體系的超級父類不易於維護)。

                  2,裝飾器的抽象父類Decorator並非必須的。

                 那麼咱們將上述標準的裝飾器模式,用咱們熟悉的JAVA代碼給詮釋一下。首先是待裝飾的接口Component。

[java] view plaincopy

<EMBED id=ZeroClipboardMovie_1 name=ZeroClipboardMovie_1 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer height=18 width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=1&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">

  1. package com.decorator;  

  2.   

  3. public interface Component {  

  4.   

  5.     void method();  

  6.       

  7. }  

                 接下來即是咱們的一個具體的接口實現類,也就是俗稱的原始對象,或者說待裝飾對象。

[java] view plaincopy

<EMBED id=ZeroClipboardMovie_2 name=ZeroClipboardMovie_2 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer height=18 width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=2&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">

  1. package com.decorator;  

  2.   

  3. public class ConcreteComponent implements Component{  

  4.   

  5.     public void method() {  

  6.         System.out.println("原來的方法");  

  7.     }  

  8.   

  9. }  

                 下面即是咱們的抽象裝飾器父類,它主要是爲裝飾器定義了咱們須要裝飾的目標是什麼,並對Component進行了基礎的裝飾。

[java] view plaincopy

<EMBED id=ZeroClipboardMovie_3 name=ZeroClipboardMovie_3 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer height=18 width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=3&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">

  1. package com.decorator;  

  2.   

  3. public abstract class Decorator implements Component{  

  4.   

  5.     protected Component component;  

  6.   

  7.     public Decorator(Component component) {  

  8.         super();  

  9.         this.component = component;  

  10.     }  

  11.   

  12.     public void method() {  

  13.         component.method();  

  14.     }  

  15.       

  16. }  

                  再來即是咱們具體的裝飾器A和裝飾器B。

[java] view plaincopy

<EMBED id=ZeroClipboardMovie_4 name=ZeroClipboardMovie_4 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer height=18 width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=4&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">

  1. package com.decorator;  

  2.   

  3. public class ConcreteDecoratorA extends Decorator{  

  4.   

  5.     public ConcreteDecoratorA(Component component) {  

  6.         super(component);  

  7.     }  

  8.       

  9.     public void methodA(){  

  10.         System.out.println("被裝飾器A擴展的功能");  

  11.     }  

  12.   

  13.     public void method(){  

  14.         System.out.println("針對該方法加一層A包裝");  

  15.         super.method();  

  16.         System.out.println("A包裝結束");  

  17.     }  

  18. }  

[java] view plaincopy

<EMBED id=ZeroClipboardMovie_5 name=ZeroClipboardMovie_5 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer height=18 width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=5&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">

  1. package com.decorator;  

  2.   

  3. public class ConcreteDecoratorB extends Decorator{  

  4.   

  5.     public ConcreteDecoratorB(Component component) {  

  6.         super(component);  

  7.     }  

  8.       

  9.     public void methodB(){  

  10.         System.out.println("被裝飾器B擴展的功能");  

  11.     }  

  12.   

  13.     public void method(){  

  14.         System.out.println("針對該方法加一層B包裝");  

  15.         super.method();  

  16.         System.out.println("B包裝結束");  

  17.     }  

  18. }  

                下面給出咱們的測試類。咱們針對多種狀況進行包裝。

[java] view plaincopy

<EMBED id=ZeroClipboardMovie_6 name=ZeroClipboardMovie_6 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer height=18 width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=6&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">

  1. package com.decorator;  

  2.   

  3. public class Main {  

  4.   

  5.     public static void main(String[] args) {  

  6.         Component component =new ConcreteComponent();//原來的對象  

  7.         System.out.println("------------------------------");  

  8.         component.method();//原來的方法  

  9.         ConcreteDecoratorA concreteDecoratorA = new ConcreteDecoratorA(component);//裝飾成A  

  10.         System.out.println("------------------------------");  

  11.         concreteDecoratorA.method();//原來的方法  

  12.         concreteDecoratorA.methodA();//裝飾成A之後新增的方法  

  13.         ConcreteDecoratorB concreteDecoratorB = new ConcreteDecoratorB(component);//裝飾成B  

  14.         System.out.println("------------------------------");  

  15.         concreteDecoratorB.method();//原來的方法  

  16.         concreteDecoratorB.methodB();//裝飾成B之後新增的方法  

  17.         concreteDecoratorB = new ConcreteDecoratorB(concreteDecoratorA);//裝飾成A之後再裝飾成B  

  18.         System.out.println("------------------------------");  

  19.         concreteDecoratorB.method();//原來的方法  

  20.         concreteDecoratorB.methodB();//裝飾成B之後新增的方法  

  21.     }  

  22. }  

                 下面看下咱們運行的結果,究竟是產生了什麼效果。

               今後能夠看到,咱們首先是使用的原始的類的方法,而後分別讓A和B裝飾完之後再調用,最後咱們將兩個裝飾器一塊兒使用,再調用該接口定義的方法。

               上述當中,咱們分別對待裝飾類進行了原方法的裝飾和新功能的增長,methodA和methodB就是新增長的功能,這些都是裝飾器能夠作的,固然二者並不必定兼有,但通常至少會有一種,不然也就失去了裝飾的意義。

               另外,文章開篇就說道了IO與裝飾器的情緣,相信各位就算不太清楚,也都大體據說過JAVA的IO是裝飾器模式實現的,因此LZ也再也不廢話,在給出一個標準的模板示例之後,直接拿出IO的示例,咱們真槍實彈的來。

               下面LZ直接給出IO包中的部分裝飾過程,上面LZ加了詳細的註釋以及各個裝飾器的功能演示,各位能夠和上面標準的裝飾器模式對比一下,LZ不得不感嘆,IO與裝飾器的孽緣。

[java] view plaincopy

<EMBED id=ZeroClipboardMovie_7 name=ZeroClipboardMovie_7 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer height=18 width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=7&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">

  1. package com.decorator;  

  2.   

  3. import java.io.BufferedInputStream;  

  4. import java.io.BufferedReader;  

  5. import java.io.DataInputStream;  

  6. import java.io.FileInputStream;  

  7. import java.io.IOException;  

  8. import java.io.InputStream;  

  9. import java.io.InputStreamReader;  

  10. import java.io.LineNumberReader;  

  11. import java.io.PushbackInputStream;  

  12. import java.io.PushbackReader;  

  13.   

  14. public class IOTest {  

  15.   

  16.     /* test.txt內容: 

  17.      * hello world! 

  18.      */  

  19.     public static void main(String[] args) throws IOException, ClassNotFoundException {  

  20.         //文件路徑可自行更換  

  21.         final String filePath = "E:/myeclipse project/POITest/src/com/decorator/test.txt";  

  22.           

  23.         //InputStream至關於被裝飾的接口或者抽象類,FileInputStream至關於原始的待裝飾的對象,FileInputStream沒法裝飾InputStream  

  24.         //另外FileInputStream是以只讀方式打開了一個文件,並打開了一個文件的句柄存放在FileDescriptor對象的handle屬性  

  25.         //因此下面有關回退和從新標記等操做,都是在堆中創建緩衝區所形成的假象,並非真正的文件流在回退或者從新標記  

  26.         InputStream inputStream = new FileInputStream(filePath);  

  27.         final int len = inputStream.available();//記錄一下流的長度  

  28.         System.out.println("FileInputStream不支持mark和reset:" + inputStream.markSupported());  

  29.           

  30.         System.out.println("---------------------------------------------------------------------------------");  

  31.           

  32.         /* 下面分別展現三種裝飾器的做用BufferedInputStream,DataInputStream,PushbackInputStream,LZ下面作了三個裝飾器的功能演示  */  

  33.           

  34.         //首先裝飾成BufferedInputStream,它提供咱們mark,reset的功能  

  35.         BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);//裝飾成 BufferedInputStream  

  36.         System.out.println("BufferedInputStream支持mark和reset:" + bufferedInputStream.markSupported());  

  37.         bufferedInputStream.mark(0);//標記一下  

  38.         char c = (char) bufferedInputStream.read();  

  39.         System.out.println("LZ文件的第一個字符:" + c);  

  40.         bufferedInputStream.reset();//重置  

  41.         c = (char) bufferedInputStream.read();//再讀  

  42.         System.out.println("重置之後再讀一個字符,依然會是第一個字符:" + c);  

  43.         bufferedInputStream.reset();  

  44.           

  45.         System.out.println("---------------------------------------------------------------------------------");  

  46.           

  47.         //裝飾成 DataInputStream,咱們爲了又使用DataInputStream,又使用BufferedInputStream的mark reset功能,因此咱們再進行一層包裝  

  48.         //注意,這裏若是不使用BufferedInputStream,而使用原始的InputStream,read方法返回的結果會是-1,即已經讀取結束  

  49.         //由於BufferedInputStream已經將文本的內容讀取完畢,並緩衝到堆上,默認的初始緩衝區大小是8192B  

  50.         DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);  

  51.         dataInputStream.reset();//這是BufferedInputStream提供的功能,若是不在這個基礎上包裝會出錯  

  52.         System.out.println("DataInputStream如今具備readInt,readChar,readUTF等功能");  

  53.         int value = dataInputStream.readInt();//讀出來一個int,包含四個字節  

  54.         //咱們轉換成字符依次顯示出來,能夠看到LZ文件的前四個字符  

  55.         String binary = Integer.toBinaryString(value);  

  56.         int first = binary.length() % 8;  

  57.         System.out.print("使用readInt讀取的前四個字符:");  

  58.         for (int i = 0; i < 4; i++) {  

  59.             if (i == 0) {  

  60.                 System.out.print(((char)Integer.valueOf(binary.substring(0, first), 2).intValue()));  

  61.             }else {  

  62.                 System.out.print(((char)Integer.valueOf(binary.substring(( i - 1 ) * 8 + first, i * 8 + first), 2).intValue()));  

  63.             }  

  64.         }  

  65.         System.out.println();  

  66.           

  67.         System.out.println("---------------------------------------------------------------------------------");  

  68.           

  69.         //PushbackInputStream沒法包裝BufferedInputStream支持mark reset,由於它覆蓋了reset和mark方法  

  70.         //由於流已經被讀取到末尾,因此咱們必須從新打開一個文件的句柄,即FileInputStream  

  71.         inputStream = new FileInputStream(filePath);  

  72.         PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream,len);//裝飾成 PushbackInputStream  

  73.         System.out.println("PushbackInputStream裝飾之後支持退回操做unread");  

  74.         byte[] bytes = new byte[len];  

  75.         pushbackInputStream.read(bytes);//讀完了整個流  

  76.         System.out.println("unread回退前的內容:" + new String(bytes));  

  77.         pushbackInputStream.unread(bytes);//再退回去  

  78.         bytes = new byte[len];//清空byte數組  

  79.         pushbackInputStream.read(bytes);//再讀  

  80.         System.out.println("unread回退後的內容:" + new String(bytes));  

  81.           

  82.         System.out.println("---------------------------------------------------------------------------------");  

  83.           

  84.         /*  以上有兩個一層裝飾和一個兩層裝飾,下面咱們先裝飾成Reader,再進行其它裝飾   */  

  85.           

  86.         //因爲以前被PushbackInputStream將流讀取到末尾,咱們須要再次從新打開文件句柄  

  87.         inputStream = new FileInputStream(filePath);  

  88.         InputStreamReader inputStreamReader = new InputStreamReader(inputStream,"utf-8");//先裝飾成InputStreamReader  

  89.         System.out.println("InputStreamReader有reader的功能,好比轉碼:" + inputStreamReader.getEncoding());  

  90.           

  91.         System.out.println("---------------------------------------------------------------------------------");  

  92.           

  93.         BufferedReader bufferedReader = new BufferedReader(inputStreamReader);//咱們進一步在reader的基礎上裝飾成BufferedReader  

  94.         System.out.println("BufferedReader有readLine等功能:" + bufferedReader.readLine());  

  95.           

  96.         System.out.println("---------------------------------------------------------------------------------");  

  97.           

  98.         LineNumberReader lineNumberReader = new LineNumberReader(inputStreamReader);//咱們進一步在reader的基礎上裝飾成LineNumberReader  

  99.         System.out.println("LineNumberReader有設置行號,獲取行號等功能(行號從0開始),當前行號:" + lineNumberReader.getLineNumber());  

  100.           

  101.         System.out.println("---------------------------------------------------------------------------------");  

  102.           

  103.         //此處因爲剛纔被readLine方法將流讀取到末尾,因此咱們再次從新打開文件句柄,並須要將inputstream再次包裝成reader  

  104.         inputStreamReader = new InputStreamReader(new FileInputStream(filePath));  

  105.         PushbackReader pushbackReader = new PushbackReader(inputStreamReader,len);//咱們進一步在reader的基礎上裝飾成PushbackReader  

  106.         System.out.println("PushbackReader是擁有退回操做的reader對象");  

  107.         char[] chars = new char[len];  

  108.         pushbackReader.read(chars);  

  109.         System.out.println("unread回退前的內容:" + new String(chars));  

  110.         pushbackReader.unread(chars);//再退回去  

  111.         chars = new char[len];//清空char數組  

  112.         pushbackReader.read(chars);//再讀  

  113.         System.out.println("unread回退後的內容:" + new String(chars));  

  114.     }  

  115. }  

                     上述即是IO的裝飾器使用,其中InputStream就至關於上述的Component接口,只不過這裏是一個抽象類,這是咱們裝飾的目標抽象類。FileInputstream就是一個ConcreteComponent,即待裝飾的具體對象,它並非JAVA的IO結構中的一個裝飾器,由於它沒法裝飾InputStream。剩下BufferedInputStream,DataInputstream等等就是各類裝飾器了,對比上述的標準裝飾器樣板,JAVA的IO中也有抽象的裝飾器基類的存在,只是上述沒有體現出來,就是FilterInputStream,它是不少裝飾器最基礎的裝飾基類。

                     在上述過程當中,其中dataInputStream是通過兩次裝飾後獲得的,它具備了dataInputStream和bufferedInputStream的雙重功能,另外,InputStreamReader是一個特殊的裝飾器,它提供了字節流到字符流的橋樑,其實它除了具備裝飾器的特色之外,也有點像一個適配器,但LZ仍是以爲它應當算是一個裝飾器。

                    其它的IO裝飾器各位能夠自行嘗試或者和上述的標準的裝飾器模式代碼比對一下,下面另附LZ的IO裝飾器程序運行後結果。

                     從上面的展現中,已經能夠充分體會到裝飾器模式的靈活了,咱們建立的一個FileInputstream對象,咱們可使用各類裝飾器讓它具備不一樣的特別的功能,這正是動態擴展一個類的功能的最佳體現,而裝飾器模式的靈活性正是JAVA中IO所須要的,不得不讚一下JAVA類庫的建造者實在是強悍。

                     上述的XXXXInputStream的各個類都繼承了InputStream,這樣作不只是爲了複用InputStream的父類功能(InputStream也是一種模板方法模式,它定義了read(byte[])方法的簡單算法,並將read()方法交給具體的InputStream去實現),也是爲了能夠重疊裝飾,即裝飾器也能夠再次被裝飾,而過渡到Reader之後,Reader的裝飾器體系則是相似的。

                     下面LZ給出上面IO包中所涉及的類的類圖,各位能夠自行和上面的標準裝飾器模式對比一下。


                     LZ在類圖上標註了各個類負責的角色,而且使用背景顏色將InputStream和Reader體系分開,其中左半部分就是InputStream的裝飾體系,右半部分就是Reader的裝飾體系,而且他們之間的橋樑是InputStreamReader,他們每個裝飾體系都與上面標準的裝飾器模式類圖極其類似,各位能夠本身看一下,感覺一下,尤爲是InputStreamReader,它的位置比較特殊。

                     總之呢,裝飾器模式就是一個能夠很是靈活的動態擴展類功能的設計模式,它採用組合的方式取代繼承,使得各個功能的擴展更加獨立和靈活。

                     本次裝飾器模式就到此結束了,感謝各位的收看,下期再見。

                     下期預告,外觀模式。   

相關文章
相關標籤/搜索