飛翔的起點

          從這里出發(fā)

          導航

          <2008年4月>
          303112345
          6789101112
          13141516171819
          20212223242526
          27282930123
          45678910

          統(tǒng)計

          常用鏈接

          留言簿(5)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          java I/O學習

           我想任何一本介紹模式的書在講到Decorator模式的時候不能不提到它的實際應用——在Java/IO庫里面的應用,<<Java與模式>>這本書也不例外,有點不一樣的是,這本書在介紹的時候有個專題,是從兩個模式來看Java/IO庫,完這個專題后,個人感覺對Java/IO庫有了全新的認識同時也加深了Decorator模式跟Adapter適配器模式的理解,現(xiàn)和大家分享下這個在我看來很偉大的成果,同時說明下,以下大部分文字跟圖片是來自<<Java與模式>>這本書。

            一。引子(概括地介紹Java的IO)

            無論是哪種編程語言,輸入跟輸出都是重要的一部分,Java也不例外,而且Java將輸入/輸出的功能和使用范疇做了很大的擴充。它采用了流的機制來實現(xiàn)輸入/輸出,所謂流,就是數(shù)據(jù)的有序排列,而流可以是從某個源(稱為流源或Source of Stream)出來,到某個目的地(稱為流匯或Sink of Stream)去的。由流的方向,可以分成輸入流和輸出流,一個程序從輸入流讀取數(shù)據(jù)向輸出流寫數(shù)據(jù)。

            如,一個程序可以用FileInputStream類從一個磁盤文件讀取數(shù)據(jù),如下圖所示:

            像FileInputStream這樣的處理器叫做流處理器,它就像流的管道一樣,從一個流源吸入某種類型的數(shù)據(jù),并輸出某種類型的數(shù)據(jù)。上面這種示意圖叫做流的管道圖。

            同樣道理,也可以用FileOutputStream類向一個磁盤文件寫數(shù)據(jù),如下圖所示:

            在實際應用這種機制并不沒有太大的用處,程序需要寫出地通常是非常結(jié)構(gòu)化的信息,因此這些byte類型的數(shù)據(jù)實際上是一些數(shù)值,文字,源代碼等。Java的I/O庫提供了一個稱做鏈接(Chaining)的機制,可以將一個流處理器跟另一個流處理器首尾相接,以其中之一的輸出為輸入,形成一個流管道的鏈接。

            例如,DataInputStream流處理器可以把FileInputStream流對象的輸出當作輸入,將Byte類型的數(shù)據(jù)轉(zhuǎn)換成Java的原始類型和String類型的數(shù)據(jù)。如下圖所示:

            類似地,向一個文件寫入Byte類型的數(shù)據(jù)不是一個簡單的過程。一個程序需要向一個文件里寫入的數(shù)據(jù)往往都是結(jié)構(gòu)化的,而Byte類型則是原始類型。因此在寫的時候必須經(jīng)過轉(zhuǎn)換。DataOutputStream流處理器提供了接收了原始數(shù)據(jù)類型和String數(shù)據(jù)類型,而這個流處理器的輸出數(shù)據(jù)則是Byte類型。也就是說DataOutputStream可以將源數(shù)據(jù)轉(zhuǎn)換成Byte類型的數(shù)據(jù),再輸出來。

            這樣一來,就可以將DataOutputStream與FileOutputStream鏈接起來,這樣程序就可以將原始數(shù)據(jù)類型和String類型的源數(shù)據(jù)寫入這個鏈接好的雙重管道里面,達到將結(jié)構(gòu)化數(shù)據(jù)寫到磁盤文件里面的目的,如下圖所示:

            這又是鏈接的所發(fā)揮的大作用。

            流處理器所處理的流必定都有流源,而如果將流類所處理的流源分類的話,基本可以分成兩大類:

            第一 數(shù)組,String,F(xiàn)ile等,這一種叫原始流源。

            第二 同樣類型的流用做鏈接流類的流源,叫鏈接流源。

            二 Java I/O庫的設(shè)計原則

            Java語言的I/O庫是對各種常見的流源,流匯以及處理過程的抽象化。客戶端的Java程序不必知道最終的流源,流匯是磁盤上的文件還是數(shù)組等;也不必關(guān)心數(shù)據(jù)是否經(jīng)過緩沖的,可否按照行號讀取等處理的細節(jié)。

            書中提到了,對于第一次見到Java/IO庫的人,無不因為這個庫的龐雜而感到困惑;而對于熟悉這個庫的人,而又常常為這個庫的設(shè)計是否得當而爭論不體。書的作者提出自己的意見,要理解Java I/O這個龐大而復雜的庫,關(guān)鍵是要掌握兩個對稱性跟兩個設(shè)計模式模式。

            Java I/O庫具有兩個對稱性,它們分別是:

            1 輸入-輸出對稱性,比如InputStream和OutputStream各自占據(jù)Byte流的輸入與輸出的兩個平行的等級結(jié)構(gòu)的根部。而Reader和Writer各自占據(jù)Char流的輸入與輸出的兩個平行的等級結(jié)構(gòu)的根部。

            2 byte-char對稱,InputStream和Reader的子類分別負責Byte和Char流的輸入;OutputStream和Writer的子類分別負責Byte和Char流的輸出,它們分別形成平行的等級結(jié)構(gòu)。

            Java I/O庫的兩個設(shè)計模式:

            Java的I/O庫總體設(shè)計是符合裝飾者模式(Decorator)跟適配器模式(Adapter)的。如前所述,這個庫中處理流的類叫做流類。引子里所談到的FileInputStream,F(xiàn)ileOutputStream,DataInputStream及DataOutputStream都是流處理器的例子。

            1 裝飾者模式:在由InputStream,OutputStream,Reader和Writer代表的等級結(jié)構(gòu)內(nèi)部,有一些流處理器可以對另一些流處理器起到裝飾作用,形成新的,具有改善了的功能的流處理器。裝飾者模式是Java I/O庫的整體設(shè)計模式。這樣的一個原則是符合裝飾者模式的,如下圖所示:

            2 適配器模式:在由InputStream,OutputStream,Reader和Writer代表的等級結(jié)構(gòu)內(nèi)部,有一些流處理器是對其它類型的流源的適配。這就是適配器模式的應用,如下圖所示。

            適配器模式應用到了原始流處理器的設(shè)計上面,構(gòu)成了I/O庫所有流處理器的起點。

            JDK為程序員提供了大量的類庫,而為了保持類庫的可重用性,可擴展性和靈活性,其中使用到了大量的設(shè)計模式,本文將介紹JDK的I/O包中使用到的Decorator模式,并運用此模式,實現(xiàn)一個新的輸出流類。

            Decorator模式簡介

            Decorator模式又名包裝器(Wrapper),它的主要用途在于給一個對象動態(tài)的添加一些額外的職責。與生成子類相比,它更具有靈活性。

            有時候,我們需要為一個對象而不是整個類添加一些新的功能,比如,給一個文本區(qū)添加一個滾動條的功能。我們可以使用繼承機制來實現(xiàn)這一功能,但是這種方法不夠靈活,我們無法控制文本區(qū)加滾動條的方式和時機。而且當文本區(qū)需要添加更多的功能時,比如邊框等,需要創(chuàng)建新的類,而當需要組合使用這些功能時無疑將會引起類的爆炸。

            我們可以使用一種更為靈活的方法,就是把文本區(qū)嵌入到滾動條中。而這個滾動條的類就相當于對文本區(qū)的一個裝飾。這個裝飾(滾動條)必須與被裝飾的組件(文本區(qū))繼承自同一個接口,這樣,用戶就不必關(guān)心裝飾的實現(xiàn),因為這對他們來說是透明的。裝飾會將用戶的請求轉(zhuǎn)發(fā)給相應的組件(即調(diào)用相關(guān)的方法),并可能在轉(zhuǎn)發(fā)的前后做一些額外的動作(如添加滾動條)。通過這種方法,我們可以根據(jù)組合對文本區(qū)嵌套不同的裝飾,從而添加任意多的功能。這種動態(tài)的對對象添加功能的方法不會引起類的爆炸,也具有了更多的靈活性。

            以上的方法就是Decorator模式,它通過給對象添加裝飾來動態(tài)的添加新的功能。如下是Decorator模式的UML圖:

            Component為組件和裝飾的公共父類,它定義了子類必須實現(xiàn)的方法。

            ConcreteComponent是一個具體的組件類,可以通過給它添加裝飾來增加新的功能。

            Decorator是所有裝飾的公共父類,它定義了所有裝飾必須實現(xiàn)的方法,同時,它還保存了一個對于Component的引用,以便將用戶的請求轉(zhuǎn)發(fā)給Component,并可能在轉(zhuǎn)發(fā)請求前后執(zhí)行一些附加的動作。

            ConcreteDecoratorA和ConcreteDecoratorB是具體的裝飾,可以使用它們來裝飾具體的Component.

            JAVA IO包中的Decorator模式

            JDK提供的java.io包中使用了Decorator模式來實現(xiàn)對各種輸入輸出流的封裝。以下將以java.io.OutputStream及其子類為例,討論一下Decorator模式在IO中的使用。

            首先來看一段用來創(chuàng)建IO流的代碼:

            以下是代碼片段:

            try {

            OutputStream out = new DataOutputStream(new FileOutputStream("test.txt"));

            } catch (FileNotFoundException e) {

            e.printStackTrace();

            }

            這段代碼對于使用過JAVA輸入輸出流的人來說再熟悉不過了,我們使用DataOutputStream封裝了一個FileOutputStream.這是一個典型的Decorator模式的使用,F(xiàn)ileOutputStream相當于Component,DataOutputStream就是一個Decorator.將代碼改成如下,將會更容易理解:

            以下是代碼片段:

            try {

            OutputStream out = new FileOutputStream("test.txt");

            out = new DataOutputStream(out);

            } catch(FileNotFoundException e) {

            e.printStatckTrace();

            }

            由于FileOutputStream和DataOutputStream有公共的父類OutputStream,因此對對象的裝飾對于用戶來說幾乎是透明的。下面就來看看OutputStream及其子類是如何構(gòu)成Decorator模式的:

            OutputStream是一個抽象類,它是所有輸出流的公共父類,其源代碼如下:

            以下是代碼片段:

            public abstract class OutputStream implements Closeable, Flushable {

            public abstract void write(int b) throws IOException;

            ……

            }

            它定義了write(int b)的抽象方法。這相當于Decorator模式中的Component類。

            ByteArrayOutputStream,F(xiàn)ileOutputStream 和 PipedOutputStream 三個類都直接從OutputStream繼承,以ByteArrayOutputStream為例:

            以下是代碼片段:

            public class ByteArrayOutputStream extends OutputStream {

            protected byte buf[];

            protected int count;

            public ByteArrayOutputStream() {

            this(32);

            }

            public ByteArrayOutputStream(int size) {

            if (size 〈 0) {

            throw new IllegalArgumentException("Negative initial size: "

            + size);

            }

            buf = new byte[size];

            }

            public synchronized void write(int b) {

            int newcount = count + 1;

            if (newcount 〉 buf.length) {

            byte newbuf[] = new byte[Math.max(buf.length 〈〈 1, newcount)];

            System.arraycopy(buf, 0, newbuf, 0, count);

            buf = newbuf;

            }

            buf[count] = (byte)b;

            count = newcount;

            }

            ……

            }

            它實現(xiàn)了OutputStream中的write(int b)方法,因此我們可以用來創(chuàng)建輸出流的對象,并完成特定格式的輸出。它相當于Decorator模式中的ConcreteComponent類。

            接著來看一下FilterOutputStream,代碼如下:

            以下是代碼片段:

            public class FilterOutputStream extends OutputStream {

            protected OutputStream out;

            public FilterOutputStream(OutputStream out) {

            this.out = out;

            }

            public void write(int b) throws IOException {

            out.write(b);

            }

            ……

            }

            同樣,它也是從OutputStream繼承。但是,它的構(gòu)造函數(shù)很特別,需要傳遞一個OutputStream的引用給它,并且它將保存對此對象的引用。而如果沒有具體的OutputStream對象存在,我們將無法創(chuàng)建FilterOutputStream.由于out既可以是指向FilterOutputStream類型的引用,也可以是指向ByteArrayOutputStream等具體輸出流類的引用,因此使用多層嵌套的方式,我們可以為ByteArrayOutputStream添加多種裝飾。這個FilterOutputStream類相當于Decorator模式中的Decorator類,它的write(int b)方法只是簡單的調(diào)用了傳入的流的write(int b)方法,而沒有做更多的處理,因此它本質(zhì)上沒有對流進行裝飾,所以繼承它的子類必須覆蓋此方法,以達到裝飾的目的。

            BufferedOutputStream 和 DataOutputStream是FilterOutputStream的兩個子類,它們相當于Decorator模式中的ConcreteDecorator,并對傳入的輸出流做了不同的裝飾。以BufferedOutputStream類為例:

            以下是代碼片段:

            public class BufferedOutputStream extends FilterOutputStream {

            ……

            private void flushBuffer() throws IOException {

            if (count 〉 0) {

            out.write(buf, 0, count);

            count = 0;

            }

            }

            public synchronized void write(int b) throws IOException {

            if (count 〉= buf.length) {

            flushBuffer();

            }

            buf[count++] = (byte)b;

            }

            ……

            }

            這個類提供了一個緩存機制,等到緩存的容量達到一定的字節(jié)數(shù)時才寫入輸出流。首先它繼承了FilterOutputStream,并且覆蓋了父類的write(int b)方法,在調(diào)用輸出流寫出數(shù)據(jù)前都會檢查緩存是否已滿,如果未滿,則不寫。這樣就實現(xiàn)了對輸出流對象動態(tài)的添加新功能的目的。

            下面,將使用Decorator模式,為IO寫一個新的輸出流。

            自己寫一個新的輸出流

            了解了OutputStream及其子類的結(jié)構(gòu)原理后,我們可以寫一個新的輸出流,來添加新的功能。這部分中將給出一個新的輸出流的例子,它將過濾待輸出語句中的空格符號。比如需要輸出"java io OutputStream",則過濾后的輸出為"javaioOutputStream".以下為SkipSpaceOutputStream類的代碼:

            以下是代碼片段:

            import java.io.FilterOutputStream;

            import java.io.IOException;

            import java.io.OutputStream;

            /**

            * A new output stream, which will check the space character

            * and won‘t write it to the output stream.

            * @author Magic

            *

            */

            public class SkipSpaceOutputStream extends FilterOutputStream {

            public SkipSpaceOutputStream(OutputStream out) {

            super(out);

            }

            /**

            * Rewrite the method in the parent class, and

            * skip the space character.

            */

            public void write(int b) throws IOException{

            if(b!=‘ ’){

            super.write(b);

            }

            }

            }

            它從FilterOutputStream繼承,并且重寫了它的write(int b)方法。在write(int b)方法中首先對輸入字符進行了檢查,如果不是空格,則輸出。

            以下是一個測試程序:

            以下是代碼片段:

            import java.io.BufferedInputStream;

            import java.io.DataInputStream;

            import java.io.DataOutputStream;

            import java.io.IOException;

            import java.io.InputStream;

            import java.io.OutputStream;

            /**

            * Test the SkipSpaceOutputStream.

            * @author Magic

            *

            */

            public class Test {

            public static void main(String[] args){

            byte[] buffer = new byte[1024];

            /**

            * Create input stream from the standard input.

            */

            InputStream in = new BufferedInputStream(new DataInputStream(System.in));

            /**

            * write to the standard output.

            */

            OutputStream out = new SkipSpaceOutputStream(new DataOutputStream(System.out));

            try {

            System.out.println("Please input your words: ");

            int n = in.read(buffer,0,buffer.length);

            for(int i=0;i〈n;i++){

            out.write(buffer[i]);

            }

            } catch (IOException e) {

            e.printStackTrace();

            }

            }

            }

            執(zhí)行以上測試程序,將要求用戶在console窗口中輸入信息,程序?qū)⑦^濾掉信息中的空格,并將最后的結(jié)果輸出到console窗口。比如:

            以下是引用片段:

            Please input your words:

            a b c d e f

            abcdef

            總   結(jié)

            在java.io包中,不僅OutputStream用到了Decorator設(shè)計模式,InputStream,Reader,Writer等都用到了此模式。而作為一個靈活的,可擴展的類庫,JDK中使用了大量的設(shè)計模式,比如在Swing包中的MVC模式,RMI中的Proxy模式等等。對于JDK中模式的研究不僅能加深對于模式的理解,而且還有利于更透徹的了解類庫的結(jié)構(gòu)和組成。

          posted on 2008-04-17 17:44 forgood 閱讀(122) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導航:
           
          主站蜘蛛池模板: 乐清市| 广西| 永泰县| 浏阳市| 乐业县| 云梦县| 河北区| 大丰市| 纳雍县| 嵩明县| 德格县| 涟源市| 区。| 临高县| 石屏县| 荆门市| 龙井市| 怀宁县| 柳林县| 彰化县| 邵东县| 抚顺市| 江城| 平舆县| 贡觉县| 泗阳县| 江北区| 扎鲁特旗| 汤原县| 淮阳县| 新河县| 远安县| 新丰县| 东莞市| 新密市| 宁都县| 庆元县| 林甸县| 安泽县| 扎鲁特旗| 秀山|