裝飾者模式的詳解
裝飾者模式動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性
的替代方案。
裝飾者模式設計類之間的關系:
其中Component是一個超類,ConcreteComponen是被裝飾者,Decorator是裝飾者,裝飾者和被裝飾者有共同的超類Component,但是此時我們發現Decorator和Component還有另外一條線,這條線表示Decorator還要組合Component。
在我看來裝飾的模式中這幾個類的主要作用如下:
首先我們看一個這樣的現象(這個是我在網上看到的例子,我把它引入進來主要是方便談談我的理解)
現象描述:
如果說繼承里的超類是一個模具(Component),做出各種各樣的稍有不同的子類成品(ConcreteComponen,ConcreteDecoratorA,ConcreteDecoratorB)來滿足各種各樣的功能。那么把裝飾模式比作的一條生產線吧,一個產品(ConcreteComponen)傳過來,在各道工序不斷地給它加上新的功能,把它一步步按照順序包裝成一個全新的產品(Concrete DecoratorA,Concrete DecoratorB)。記住,這條生產線可能是無限長的,這種包裝也可以是無限添加的。
我的理解
1. Component是超類,他出現的意義是什么呢?照理說Decorator直接去組合ConcreteComponent,就可以去加強ConcreteComponent的行為,成而加工出一個不通的子類成品。但是可能有這樣一種情況,ConcreteComponent的平級的區域內還有ConcreteComponent1,ConcreteComponent2……這樣要為每一個ConcreteComponent都去寫一個Decorator嗎?這樣顯然不可能。所以抽象出Component的一個好處就是讓ConcreteComponent和Decorator彼此解耦。Decorator只要得到Component對象的引用即可。
2. Decorator是裝飾者,而真正裝飾的過程是在Decorator的子類ConcreteDecoratorA和ConcreteDecoratorB中完成的,他們使用不同的裝飾方法,作出了不同的子類成品。Decorator的作用只是告訴大家,我要去裝飾ConcreteComponent,或許還會提供一些用這個裝飾者去裝飾的而形成的成品共用的特性和功能。
3. 裝飾者永遠是裝飾者,但裝飾者裝飾出來的成品也有可能會變成被裝飾者。即ConcreteDecoratorA和ConcreteDecoratorB有一天也會變到ConcreteComponen的地位。
只不過這個時候在ConcreteDecoratorA的旁邊可能會出現一個類似於Decorator的裝飾者DecoratorA,它同樣要繼承並組合超類Component。目的是相同的:解耦,讓一個裝飾者不必為一個被裝飾者而存在
這時候的裝飾者模式的結構圖變成:
由上圖說明一個道理,如果條件容許的話Decorator和DecoratorA的位置是可以互相交換的。這就像沖一杯帶糖的奶粉,Decorator表示放糖,DecoratorA表示放奶粉,其實是先放糖還是先放奶粉,最終都能得到ConcreteDecoratorAA(一杯帶糖的奶粉)。當然這是在條件容許的情況下,在有些生產環境中,制做工序(裝飾的先后)是不能亂的。
裝飾者模式在JAVA IO中的應用
先給出Java IO包中主要類的關系:
java IO包中有四大等級結構InputStream,outputStream, InputStreamReader,outputStreamReader。InputStream和OutputStream處理8位字節流數據, Reader和Writer處理16位的字符流數據。InputStream和Reader處理輸入, OutputStream和Writer處理輸出。各個體系內部用到的都是裝飾者模式,而InputStream和InputStreamReader之間,outputStream和outputStreamReader之間用的是適配器模式
下面主要以InputStream和InputStreamReader為例詳解
1. 從裝飾者模式看InputStream結構
InputStream的類圖關系
class java.lang.Object
|
|—class java.io.InputStream //輸入流,字節形式,為以下的基類
| |
| |——ByteArrayInputStream //從字節數組中讀取
| |
| |——FileInputStream //從文件中讀取數據
| |
| |—— FilterInputStream //過濾流的基類,
| | | // 過濾可以了解為各種處理技術的形象稱呼
| | |
| | |——BufferedInputStream //緩沖技術,
| | | // 數據來自底層輸入流
| | |
| | |——DataInputStream //可讀java數據類型
| | |
| | |——PushbackInputStream //緩沖技術,
| | | // 數據來自任意輸入流
| | |
| | |——java.util.zip.GZIPInputStream
| | | //不是java.io包中的流。壓縮技術
| | |
| | |——java.security.DigestInputStream
| | | //不是java.io包中的流。處理流的摘要
| | |
| |—— .......
從圖中可以看出,InputStream就是裝飾者模式中的超類(Component),ByteArrayInputStream,FileInputStream相當於被裝飾者(ConcreteComponent),這些類都提供了最基本的字節讀取功能。
而另外一個和這兩個類是同一級的類FilterInputStream即是裝飾者(Decorator),BufferedInputStream,DataInputStream,PushbackInputStream…這些都是被裝飾者裝飾后形成的成品。
根據裝飾者模式的特點,我們可以總結出這些IO流的使用方法:
File file = new File ("hello.txt");
FileInputStream in=new FileInputStream(file);
BufferedInputStream inBuffered=new BufferedInputStream (in);
這里BufferedInputStream主要是提供了緩存機制,先讀入一個byte[],等count到達緩存Byte[]的大小的時候,再一次讀入。
當然你也可以寫成BufferedInputStream inBuffered =
new BufferedInputStream (new FileInputStream(new File ("hello.txt")));
從使用的角度來看裝飾者模式,可以看出它的一個缺點:裝飾者模式的實現對於使用者是透明的,當使用者不熟悉你的實現的時,就很難理解。
同理你可以學習一下另外一個結構outputStream
2. 適配器模式看InputStreamReader
適配器模式比較簡單就不多講了,主要是解決了java無法多繼承的問題,下面大概講一下IO包中是怎么用這個模式的,用它來做什么?
InputStreamReader和InputStream的功能的不同點在於InputStream是以二進制輸入 / 輸出, I/O 速度快且效率高,由於讀到的是字節,也就不存在亂碼問題,平台移植性好。但是它的 read ()方法讀到的是一個字節,很不利於人們閱讀。InputStreamReader類將字節轉換為字符。 你可以在構造器中指定編碼的方式,如果不指定的話將采用底層操作系統的默認編碼方式。
Java.io.Reader類提供了要求了繼承這個類的所有類必須提供
/**
* Reads characters into a portion of an array. This method will block
* until some input is available, an I/O error occurs, or the end of the
* stream is reached.
*
* @param cbuf Destination buffer
* @param off Offset at which to start storing characters
* @param len Maximum number of characters to read
*
* @return The number of characters read, or -1 if the end of the
* stream has been reached
*
* @exception IOException If an I/O error occurs
*/
abstract public int read(char cbuf[], int off, int len) throws IOException;
代碼注釋理解:讀出來的形式必須是字符,而不是字節了,InputStreamReader繼承於Reader,即具備了讀出字符的功能,而把什么讀成字節的功能就要InputStreamReader去適配了,InputStreamReader的構造函數是這樣的:
/**
* Creates an InputStreamReader that uses the named charset.
*
* @param in
* An InputStream
*
* @param charsetName
* The name of a supported
* {@link java.nio.charset.Charset </code>charset<code>}
*
* @exception UnsupportedEncodingException
* If the named charset is not supported
*/
public InputStreamReader(InputStream in, String charsetName)
throws UnsupportedEncodingException
{
super(in);
if (charsetName == null)
throw new NullPointerException("charsetName");
sd = StreamDecoder.forInputStreamReader(in, this, charsetName);
}
用的是InputStream去適配InputReader。
3. 淺談Reader體系
Reader體系中同樣用到的是裝飾者模式,但是有一點不同,Reader體系中的FilterRead類和InputStream體系中的FilterInputStream的功能不同,它不再是裝飾者。
這一點可以從BufferReader和BufferStreamReader的實現不同可以看出
bufferReader:public class BufferedReader extends Reader
BufferedInputStream:public class BufferedInputStream extends FilterInputStream
但模式還是相同的。在Reader我沒找到FilterInputStream類似作用的東西
下面看看Reader IO的使用方法
1. File file = new File ("hello.txt");
2. FileInputStream in=new FileInputStream(file);
3. InputStreamReader inReader=new InputStreamReader(in);
4. BufferedReader bufReader=new BufferedReader(inReader);
可以看出步驟2到3使用的是適配器模式,而3到4使用的是裝飾者模式
好了,以上就是我學習設計模式和java IO的心得,自己也是初學者,希望對同樣這塊不太了解的人有所幫助,有什么意見大家可以提出。