從Decorator,Adapter模式看Java/IO庫

來源:互聯網
上載者:User

轉載 http://blog.csdn.net/lin_bei/article/details/1067506  

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

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

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

 如,一個程式可以用FileInputStream類從一個磁碟檔案讀取資料,如所示:

 

 像FileInputStream這樣的處理器叫做流處理器,它就像流的管道一樣,從一個流源吸入某種類型的資料,並輸出某種類型的資料。上面這種叫做流的管道圖。

 同樣道理,也可以用FileOutputStream類向一個磁碟檔案寫資料,如所示:

  

 在實際應用這種機制並不沒有太大的用處,程式需要寫出地通常是非常結構化的資訊,因此這些byte類型的資料實際上是一些數值,文字,原始碼等。Java的I/O庫提供了一個稱做連結(Chaining)的機制,可以將一個流處理器跟另一個流處理器首尾相接,以其中之一的輸出為輸入,形成一個流管道的連結。

 例如,DataInputStream流處理器可以把FileInputStream流對象的輸出當作輸入,將Byte類型的資料轉換成Java的原始類型和String類型的資料。如所示:

 類似地,向一個檔案寫入Byte類型的資料不是一個簡單的過程。一個程式需要向一個檔案裡寫入的資料往往都是結構化的,而Byte類型則是原始類型。因此在寫的時候必須經過轉換。DataOutputStream流處理器提供了接收了未經處理資料類型和String資料類型,而這個流處理器的輸出資料則是Byte類型。也就是說DataOutputStream可以將來源資料轉換成Byte類型的資料,再輸出來。

 這樣一來,就可以將DataOutputStream與FileOutputStream連結起來,這樣程式就可以將未經處理資料類型和String類型的來源資料寫入這個連結好的雙重管道裡面,達到將結構化資料寫到磁碟檔案裡面的目的,如所示:

 

 這又是連結的所發揮的大作用。

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

 第一 數組,String,File等,這一種叫原始流源。

 第二 同樣類型的流用做連結流類的流源,叫連結流源。

 二 Java I/O庫的設計原則

 Java語言的I/O庫是對各種常見的流源,流匯以及處理過程的抽象化。用戶端的Java程式不必知道最終的流源,流匯是磁碟上的檔案還是數組等;也不必關心資料是否經過緩衝的,可否按照行號讀取等處理的細節。

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

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

 1 輸入-輸出對稱性,比如InputStream和OutputStream各自佔據Byte流的輸入與輸出的兩個平行的等級結構的根部。而Reader和Writer各自佔據Char流的輸入與輸出的兩個平行的等級結構的根部。

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

Java I/O庫的兩個設計模式:

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

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

2 適配器模式:在由InputStream,OutputStream,Reader和Writer代表的等級結構內部,有一些流處理器是對其它類型的流源的適配。這就是適配器模式的應用,如所示。

  

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

 今天晚上先到這了,明天再接著細看兩種設計模式具體是怎樣在I/O庫中被應用的。

 

三 裝飾模式的應用

 學過裝飾模式後,大家會發現,它在Java語言中最著名的應用莫過於Java I/O標準為庫的設計了。這一節將以處理Byte流為例,看看裝飾模式是怎樣得到應用的。

 為什麼不用繼承而用裝飾模式

 我們知道Java I/O庫需要很多效能的各種組合,如果說這些效能的組合是通過繼承方式來實現的話,那麼每一種組合都需要一個類,這樣就會出現大量重複性問題的出現,從而使類數目“爆炸”。而如果採用裝飾模式,那麼不僅類的數目大減少了,效能的重複也可以減至到最少。所以裝飾模式是Java I/O庫的基本模式。在這裡我想再用<<Head First Design Pattern>>中講到裝飾模式時候的一個例子,看看裝飾模式是怎麼達到不僅類的數目大減少了,效能的重複也可以減至到最少:

 它這個例子大概是說:Beverage是一個抽象類別,它被所有在一個咖啡店裡賣的飲料繼承。Beverage有個抽象方法cost,所有的子類都要實現這個抽象方法,計算它們的價格。現在有四個最基本的咖啡:HouseBlend,DarkRoast,Decaf,Espresso他們都繼承自Beverage,現在的需求是說在四個最基本的咖啡裡,每個都可以隨便地添加調味品,像steamed milk,soy,還有mocha最後是加上whipped milk。如果是說按繼承來實現這種幾個調味品跟原來咖啡的組合的話,我們會很自然地設計來下面的類圖來:

 

看到了上面的類圖了嗎,我們不禁會說這就是“類爆炸”。如果是按裝飾模式的設計思路我們可以得出下面的設計類圖:

 

我們再來看看Gof裡面的標準的裝飾模式的類圖表示:

 

仔細看看上面的幾個圖後我們肯定就會理解這句話了:裝飾模式是怎麼達到不僅類的數目大減少了,效能的重複也可以減至到最少。

 再回到Java I/O庫,由於裝飾模式的引用,造成了靈活性和複雜都大大增加了,我們在使用Java I/O庫時,必須理解Java I/O庫是由一些基本的原始流處理器和圍繞它們的裝飾流處理器所組成的,這樣可以在學習和使用Java I/O庫時達到事半功倍的效果。

 下面我用<<Java與模式>>,<<Head First Design Pattern>>或者是網上看到的一些類圖來分析:

 首先是InputStream類型中的裝飾模式:

 InputStream有七個直接的具體子類,有四個屬於FilterInputStream的具體子類,如所示:

 

  中所有的類都叫做流處理器,這個圖就叫做(InputStream類型的)流處理器圖。

  書中提到根據輸入資料流的源的類型,可以將這些流類分成兩種,即原始流類(Original Stream)和連結流處理器(Wrapper Stream)。

  原始流處理器

  原始流處理器接收一個Byte數組對象,String對象,FileDiscriptor對象或者不同類型的流來源物件,根據上面的圖,原始流處理器包括以下四種:

  ByteArrayInputStream:為多線程的通訊提供緩衝區操作功能,接收一個Byte數組作為流的源。

  FileInputStream:建立一個與檔案有關的輸入資料流。接收一個File對象作為流的源。

  PipedInputStream:可以與PipedOutputStream配合使用,用於讀入一個資料管道的資料,接收一個PipedOutputStream作為源。

  StringBufferInputStream:將一個字串緩衝區轉換為一個輸入資料流。接收一個String對象作為流的源。(JDK協助文檔上說明:已淘汰。此類未能正確地將字元轉換為位元組。從JDK1.1開始,從字串建立流的首選方法是通過StringReader類進行建立。只有字串中每個字元的低八位可以由此類使用。)

  連結流處理器

  所謂連結流處理器,就是可以接收另一個流對象作為源,並對之進行功能擴充的類。InputStream類型的連結處理器包括以下幾種,它們都接收另一個InputStream對象作為流源。

  (1)FilterInputStream稱為過濾輸入資料流,它將另一個輸入資料流作為流源。這個類的子類包括以下幾種:

  BufferedInputStream:用來從硬碟將資料讀入到一個記憶體緩衝區中,並從緩衝區提供資料。

  DataInputStream:提供基於多位元組的讀取方法,可以讀取原始類型的資料。

  LineNumberInputStream:提供帶有行計數功能的過濾輸入資料流。

  PushbackInputStream:提供特殊的功能,可以將已經讀取的位元組“推回”到輸入資料流中。

  (2)ObjectInputStream可以將使用ObjectInputStream序列化的未經處理資料類型和對象重新並行化。

  (3)SeqcueneInputStream可以將兩個已有的輸入資料流串連起來,形成一個輸入資料流,從而將多個輸入資料流排列構成一個輸入資料流序列。

  抽象結構圖

  按照上面的這種原始流處理器和連結流處理器的劃分,可以用下面的結構圖來描述它們之間的關係。

  

 

 上面的流處理器圖跟裝飾模式的結構圖有著顯而易見的相同之處。實際上InputStream類型的流處理器結構確實符合裝飾模式。 

 裝飾模式結構圖

 

  對於FilterInputStream查看JDK1.4原始碼,部分代碼如下:

Public class FilterInputStream extends InputStream {    /**     * The input stream to be filtered.     */    protected InputStream in;    protected FilterInputStream(InputStream in) {        this.in = in;    }    //其它代碼}FilterInputStream繼承了InputStream,也引用了InputStream,而它有四個子類,這就是所謂的Decorator模式

  上面這個圖向我們傳達了這個資訊:連結流連結流對象接收一個原始流對象或者另外一個連結流對象作為流源;另一方面他們對流源的內部工作方法做了相應的改變,這種改變是裝飾模式所要達到的目的。比如:

  BufferedInputStream“裝飾”了InputStream的內部工作方式,使得流的讀入操作使用了緩衝機制。在使用了緩衝機制後,不會對每一次的流讀入操作都產生一個物理的讀盤動作,從而提高了程式的效率,在汲及到物理流的讀入時,都應當使用這個裝飾流類。

  LineNumberInputStream和PushbackInputStream也同樣“裝飾”了InputStream的內部工作方式,前者使得程式能夠按照行號讀入資料;後者能夠使程式讀入的過程中,退後一個字元。

  DataInputStream子類讀入各種不同的未經處理資料類型以及String類型的資料,這一點可以從它提供的各種read方法看出來,如:readByte(),readInt(),readFloat()等。

  Java語言的I/O庫提供了四大等級結構:InputStream,OutputStream,Reader,Writer四個系列的類。InputStream和OutputStream處理8位位元組流資料, Reader和Writer處理16位的字元流資料。InputStream和Reader處理輸入, OutputStream和Writer處理輸出,所以OutputStream,Reader,Writer這三類的裝飾模式跟前面詳細介紹的InputStream裝飾模式大同小異,大家可以看書中其它部分對這三類的詳細描述或者從網上也能找到有關資料。為了方便比較這幾種類型,順便附上Java語言的I/O階層圖:

 下面的圖表示:以InputStream和OutputStream形成的層次關係

 下面的圖表示:以Reader和Writer形成的層次關係

 在下一篇文章裡將介紹適配器模式的應用。

 

四 適配器模式的應用 適配器模式是Java I/O庫中第二個最為重要的設計模式。 InputStream原始流處理器中的適配器模式 InputStream類型的原始流處理器是適配器模式的應用。 ByteArrayInputStream是一個適配器類 ByteArrayInputStream繼承了InputStream的介面,而封裝了一個byte數組。換言之,它將一個byte數組的介面適配成InputStream流處理器的介面。 我們知道Java語言支援四種類型:Java介面,Java類,Java數組,原始類型(即int,float等)。前三種是參考型別,類和數組的執行個體是對象,原始類型的值不是對象。 也即,Java語言的數組是像所有的其他對象一樣的對象,而不管數組中所儲存的元素類型是什麼。 這樣一來的話,ByteArrayInputStream就符合適配器模式的描述,是一個對象形式的適配器類。 FileInputStream是一個適配器類  在FileInputStream繼承了InputStrem類型,同時持有一個對FileDiscriptor的引用。這是將一個FileDiscriptor對象適配成InputStrem類型的對象形式的適配器模式,如所示:

     

查看JDK1.4的原始碼我們可以看到:
 Public class{ FileInputStream extends InputStream
 /* File Descriptor - handle to the open file */
   private FileDescriptor fd;
   public FileInputStream(FileDescriptor fdObj) {
     SecurityManager security = System.getSecurityManager();
     if (fdObj == null) {
throw new NullPointerException();
}
if (security != null) {
security.checkRead(fdObj);
}
fd = fdObj;
 }
 public FileInputStream(File file) throws FileNotFoundException {
String name = file.getPath();
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
fd = new FileDescriptor();
open(name);
}
//其它代碼
  StringBufferInputString繼承了InputString類型,同時持有一個對String對象的引用,這是一個將String對象適配成InputString類型的對象形式的適配器模式,如所示:     OutputStream原始流處理器中的適配器模式  同樣地,在OutputStream類型中,所有的原始流處理器都是適配器類。   ByteArrayOutputStream繼承了OutputStream類型,同時持有一個對byte數組的引用。它一個byte數組的介面適配成OutputString類型的介面,因此也是一個對象形式的適配器模式的應用。 FileOutputStream是一個適配器類FileOutputStream繼承了OutputStream類型,同時持有一個對FileDiscriptor對象的引用。這是一個將FileDiscriptor介面適配成OutputStream介面形式的對象形適配器模式。 Reader原始流處理器中的適配器模式 Reader類型的原始流處理器都是適配器模式的應用。 StringReader是一個適配器類 StringReader類繼承了Reader類型,持有一個對String對象的引用。它將String的介面適 配成Reader類型的介面,如所示:  byte流到char流的適配 在Java I/O庫中,使用比較頻繁的要數InputStreamReader,OutputStreamWriter這兩種類了, InputStreamReader是從byte輸入資料流到char輸入資料流的一個適配器。所示就是 InputStreamReader與Reader和InputStream等類的結構圖:  當把InputStreamReader與任何InputStream的具體子類連結的時候,可以從InputStream的輸出讀入byte類型的資料,將之轉換成為char類型的資料,如所示:  查看JDK1.4的InputStreamReader原始碼:
public class InputStreamReader extends Reader {
 private final StreamDecoder sd;
 /**
 * Create an InputStreamReader that uses the default charset.
  *
 * @param in   An InputStream
 */
public InputStreamReader(InputStream in) {
super(in);
 try {
 sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
 } catch (UnsupportedEncodingException e) {
 // The default encoding should always be available
throw new Error(e);
}
 //其它代碼
}
其中StreamDecoder是sun.nio.cs這個包裡的一個類
OutputStreamWriter是適配器類
同樣道理我們能得出OutputStringWriter是從OutputStream到Writer的適配器類。也就是說,與任何一個OutputStream的具體子類相連結時,OutputStringWriter可以將OutputStream類型的byte流適配成為char流。
它的原始碼跟上面的InputStreamReader差不多,這就不貼出來,感興趣可以查看JDK1.4線上源碼
這本書後面還有個小例子,附有一些講解,我就不列出來了,有書的可以看看。
 五 總結
 在這三篇文章裡主要是汲及到三個知識點:
 知識點一: Java I/O庫的四大等級結構Java語言的I/O庫提供了四大等級結構:InputStream,OutputStream,Reader,Writer四個系列的類。InputStream和OutputStream處理8位位元組流資料, Reader和Writer處理16位的字元流資料。InputStream和Reader處理輸入, OutputStream和Writer處理輸出。 知識點二: Decorator模式在Java I/O庫的應用 知識點三:  Adapter模式在Java I/O庫的應用

 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.