java中装饰器_Java设计模式12:装饰器模式
裝飾器模式
裝飾器模式又稱為包裝(Wrapper)模式。裝飾器模式以多客戶端透明的方式擴(kuò)展對象的功能,是繼承關(guān)系的一個(gè)替代方案。
裝飾器模式的結(jié)構(gòu)
通常給對象添加功能,要么直接修改對象添加相應(yīng)的功能,要么派生子類來擴(kuò)展,抑或是使用對象組合的方式。顯然,直接修改對應(yīng)的類的方式并不可取,在面向?qū)ο蟮脑O(shè)計(jì)中,我們應(yīng)該盡量使用組合對象而不是繼承對象來擴(kuò)展和復(fù)用功能,裝飾器模式就是基于對象組合的方式的。
裝飾器模式以對客戶端透明的方式動(dòng)態(tài)地給一個(gè)對象附加上了更多的責(zé)任。換言之,客戶端并不會(huì)角色對象在裝飾前和裝飾后有什么不同。裝飾器模式可以在不用創(chuàng)建更多子類的情況下,將對象的功能加以擴(kuò)展。
裝飾器模式中的角色有:
1、抽象構(gòu)件角色
給出一個(gè)抽象接口,以規(guī)范準(zhǔn)備接受附加責(zé)任的對象
2、具體構(gòu)件角色
定義一個(gè)將要接受附加責(zé)任的類
3、裝飾角色
持有一個(gè)構(gòu)建對象的實(shí)例,并定義一個(gè)與抽象構(gòu)件接口一致的接口
4、具體裝飾角色
負(fù)責(zé)給構(gòu)建對象貼上附加的責(zé)任
裝飾器模式的例子
現(xiàn)在有這么一個(gè)場景:
1、有一批廚師,簡單點(diǎn)吧,就全是中國廚師,他們有一個(gè)共同的動(dòng)作是做晚飯
2、這批廚師做晚飯前的習(xí)慣不同,有些人喜歡做晚飯前洗手、有些人喜歡做晚飯前洗頭
那么,按照裝飾器模式,先抽象出抽象構(gòu)建角色,Cook接口:
public interfaceCook {public voidcookDinner();
}
具體構(gòu)建角色,中國廚師:
public class ChineseCook implementsCook {
@Overridepublic voidcookDinner() {
System.out.println("中國人做晚飯");
}
}
定義一個(gè)裝飾器角色,具體的工作具體裝飾器去實(shí)現(xiàn),這樣,比如美國廚師做晚飯前也先洗手或者先洗頭,這兩個(gè)動(dòng)作就可以做到復(fù)用,裝飾器角色定義為FilterCook,很簡單,實(shí)現(xiàn)Cook接口并持有Cook的引用:
public abstract class FilterCook implementsCook {protectedCook cook;
}
最后定義一個(gè)具體裝飾角色,該洗手的洗手,該洗頭的洗頭:
public class WashHandsCook extendsFilterCook {publicWashHandsCook(Cook cook) {this.cook =cook;
}
@Overridepublic voidcookDinner() {
System.out.println("先洗手");
cook.cookDinner();
}
}
public class WashHearCook extendsFilterCook {publicWashHearCook(Cook cook) {this.cook =cook;
}
@Overridepublic voidcookDinner() {
System.out.println("先洗頭");
cook.cookDinner();
}
}
調(diào)用方這么實(shí)現(xiàn):
@Testpublic voidtestDecorate() {
Cook cook0= new WashHandsCook(newChineseCook());
Cook cook1= new WashHearCook(newChineseCook());
cook0.cookDinner();
cook1.cookDinner();
}
運(yùn)行結(jié)果為:
先洗手
中國人做飯
先洗頭
中國人做飯
簡單的一個(gè)例子,實(shí)現(xiàn)了裝飾器模式的兩個(gè)功能點(diǎn):
客戶端只定義了Cook接口,并不關(guān)心具體實(shí)現(xiàn)
給Chinese增加上了洗頭和洗手的動(dòng)作,且洗頭和洗手的動(dòng)作,可以給其他國家的廚師類復(fù)用
這就是裝飾器模式。
裝飾器模式與Java字節(jié)輸入流InputStream
上面的例子可能寫得不是很清楚,因此這里再繼續(xù)用代碼示例講解裝飾器模式。
裝飾器模式在Java體系中的經(jīng)典應(yīng)用是Java I/O,下面先講解字節(jié)輸入流InputStream,再講解字符輸入流Reader,希望可以通過這兩種輸入流的講解,加深對于裝飾器模式的理解。
首先看一下字節(jié)輸入流InputStream的類結(jié)構(gòu)體系:
InputStream是一個(gè)頂層的接口,文章開頭就說,裝飾器模式是繼承關(guān)系的一種替代方案,看一下為什么:
InputStream假設(shè)這里寫了兩個(gè)實(shí)現(xiàn)類,FileInputStream,ObjectInputStream分別表示文件字節(jié)輸入流,對象字節(jié)輸入流
現(xiàn)在我要給這兩個(gè)輸入流加入一點(diǎn)緩沖功能以提高輸入流效率,使用繼承的方式,那么就寫一個(gè)BufferedInputStream,繼承FileInputStream,ObjectInputStream,給它們加功能
現(xiàn)在我有另外一個(gè)需求,需要給這兩個(gè)輸入流加入一點(diǎn)網(wǎng)絡(luò)功能,那么就寫一個(gè)SocketInputStream,繼承繼承FileInputStream,ObjectInputStream,給它們加功能
這樣就導(dǎo)致兩個(gè)問題:
因?yàn)槲乙o哪個(gè)類加功能就必須繼承它,比如我要給FileInputStream,ObjectInputStream加上緩沖功能、網(wǎng)絡(luò)功能就得擴(kuò)展出2*2=4個(gè)類,更多的以此類推,這樣勢必導(dǎo)致類數(shù)量不斷膨脹
代碼無法復(fù)用,給FileInputStream,ObjectInputStream加入緩沖功能,本身代碼應(yīng)該是一樣的,現(xiàn)在卻必須繼承完畢后把一樣的代碼重寫一遍,多此一舉,代碼修改的時(shí)候必須修改多個(gè)地方,可維護(hù)性很差
所以,這個(gè)的時(shí)候我們就想到了一種解決方案:
在要擴(kuò)展的類比如BufferedInputStream中持有一個(gè)InputStream的引用,在BufferedInputStream調(diào)用InputStream中的方法,這樣擴(kuò)展的代碼就可以復(fù)用起來
將BufferedInputStream作為InputStream的子類,這樣客戶端只知道我用的是InputStream而不需要關(guān)心具體實(shí)現(xiàn),可以在客戶端不知情的情況下,擴(kuò)展InputStream的功能,加上緩沖功能
這就是裝飾器模式簡單的由來,一切都是為了解決實(shí)際問題而誕生。下一步,根據(jù)UML圖,我們來劃分一下裝飾器模式的角色。
1、InputStream是一個(gè)抽象構(gòu)件角色:
public abstract class InputStream implementsCloseable {//SKIP_BUFFER_SIZE is used to determine the size of skipBuffer
private static final int SKIP_BUFFER_SIZE = 2048;//skipBuffer is initialized in skip(long), if needed.
private static byte[] skipBuffer;
...
}
2、ByteArrayInputStream、FileInputStream、ObjectInputStream、PipedInputStream都是具體構(gòu)建角色,比如FileInputStream,它的聲明是:
public
class FileInputStream extendsInputStream
{/*File Descriptor - handle to the open file*/
privateFileDescriptor fd;private FileChannel channel = null;
...
}
3、FilterInputStream無疑就是一個(gè)裝飾角色,因?yàn)镕ilterInputStream實(shí)現(xiàn)了InputStream內(nèi)的所有抽象方法并且持有一個(gè)InputStream的引用:
public
class FilterInputStream extendsInputStream {/*** The input stream to be filtered.*/
protected volatileInputStream in;
...
}
4、具體裝飾角色就是InflaterInputStream、BufferedInputStream、DataInputStream,比如BufferedInputStream的聲明就是:
public
class BufferedInputStream extendsFilterInputStream {private static int defaultBufferSize = 8192;/*** The internal buffer array where the data is stored. When necessary,
* it may be replaced by another array of
* a different size.*/
protected volatile bytebuf[];
...
}
搞清楚具體角色之后,我們就可以這么寫了:
public static void main(String[] args) throwsException
{
File file= new File("D:/aaa.txt");
InputStream in0= newFileInputStream(file);
InputStream in1= new BufferedInputStream(newFileInputStream(file));
InputStream in2= new DataInputStream(new BufferedInputStream(newFileInputStream(file)));
}
我們這里實(shí)例化出了三個(gè)InputStream的實(shí)現(xiàn)類:
in0這個(gè)引用指向的是new出來的FileInputStream,這里簡單構(gòu)造出了一個(gè)文件字節(jié)輸入流
in1這個(gè)引用指向的是new出來的BufferedInputStream,它給FileInputStream增加了緩沖功能,使得FileInputStream讀取文件的內(nèi)容保存在內(nèi)存中,以提高讀取的功能
in2這個(gè)引用指向的是new出來的DataInputStream,它也給FileInputStream增加了功能,因?yàn)樗蠨ataInputStream和BufferedInputStream兩個(gè)附加的功能
同理,我要給ByteArrayInputStream、ObjectInputStream增加功能,也可以使用類似的方法,整個(gè)過程中,最重要的是要理解幾個(gè)問題:
哪些是具體構(gòu)建角色、哪些是具體裝飾角色,尤其是后者,區(qū)分的關(guān)鍵就是,角色中是否持有頂層接口的引用
每個(gè)具體裝飾角色有什么作用,因?yàn)橹挥兄烂總€(gè)具體裝飾角色有什么作用后,才可以知道要裝飾某個(gè)功能需要用哪個(gè)具體裝飾角色
使用構(gòu)造方法的方式將類進(jìn)行組合,給具體構(gòu)建角色加入新的功能
裝飾器模式與Java字符輸入流Reader
看完了上面的解讀,相信大家對于裝飾器模式應(yīng)當(dāng)有了一定的理解,那么再來看一下Java字符輸入流Reader,來加深對于裝飾器模式的印象。
簡單看一下Reader的類體系結(jié)構(gòu):
根據(jù)UML,分析一下每個(gè)角色:
1、抽象構(gòu)建角色
毫無疑問,由Reader來扮演,它是一個(gè)抽象類,沒有具體功能
2、具體構(gòu)建角色
由InputStreamReader、CharArrayReader、PipedReader、StringReader來扮演
3、裝飾角色
由FilterReader來扮演,但是這里要提一下這個(gè)BufferedReader,它本身也可以作為裝飾角色出現(xiàn),看一下BufferedReader的繼承關(guān)系:
public class BufferedReader extendsReader {privateReader in;private charcb[];private intnChars, nextChar;private static final int INVALIDATED = -2;private static final int UNMARKED = -1;private int markedChar =UNMARKED;
...
}
看到BufferedReader是Reader的子類,且持有Reader的引用,因此這里的BufferedReader是可以被認(rèn)為是一個(gè)裝飾角色的。
4、具體裝飾角色
BufferedReader上面提到了扮演了裝飾角色,但是也可以被認(rèn)為是一個(gè)具體裝飾角色。除了BufferedReader,具體裝飾角色還有PushbackReader。FileReader盡管也在第三行,但是FileReader構(gòu)不成一個(gè)具體裝飾角色,因?yàn)樗皇荁ufferedReader的子類也不是FilterReader的子類,不持有Reader的引用。
半透明裝飾器模式與全透明裝飾器模式
再說一下半透明裝飾器模式與全透明裝飾器模式,它們的區(qū)別是:
對于半透明裝飾器模式,裝飾后的類未必有和抽象構(gòu)件角色同樣的接口方法,它可以有自己擴(kuò)展的方法
對于全透明裝飾器模式,裝飾后的類有著和抽象構(gòu)件角色同樣的接口方法
全透明裝飾器模式是一種比較理想主義的想法,現(xiàn)實(shí)中不太可能出現(xiàn)。
比如BufferedInputStream吧,我把FileInputStream裝飾為BufferedInputStream,難道BufferedInputStream就完全沒有自己的行為?比如返回緩沖區(qū)的大小、清空緩沖區(qū)(這里只是舉個(gè)例子,實(shí)際BufferedInputStream是沒有這兩個(gè)動(dòng)作的),這些都是InputStream本身不具備的,因?yàn)镮nputStream根本不知道緩沖區(qū)這個(gè)概念,它只知道定義讀數(shù)據(jù)相關(guān)方法。
所以,更多的我們是采用半透明的裝飾器模式,即允許裝飾后的類中有屬于自己的方法,因此,前面的I/O代碼示例可以這么改動(dòng):
public static void main(String[] args) throwsException
{
File file= new File("D:/aaa.txt");
FileInputStream in0= newFileInputStream(file);
BufferedInputStream in1= new BufferedInputStream(newFileInputStream(file));
DataInputStream in2= new DataInputStream(new BufferedInputStream(newFileInputStream(file)));
}
這樣才更有現(xiàn)實(shí)意義。
裝飾器模式的優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
1、裝飾器模式與繼承關(guān)系的目的都是要擴(kuò)展對象的功能,但是裝飾器模式可以提供比繼承更多的靈活性。裝飾器模式允許系統(tǒng)動(dòng)態(tài)決定貼上一個(gè)需要的裝飾,或者除掉一個(gè)不需要的裝飾。繼承關(guān)系是不同,繼承關(guān)系是靜態(tài)的,它在系統(tǒng)運(yùn)行前就決定了
2、通過使用不同的具體裝飾器以及這些裝飾類的排列組合,設(shè)計(jì)師可以創(chuàng)造出很多不同的行為組合
缺點(diǎn)
由于使用裝飾器模式,可以比使用繼承關(guān)系需要較少數(shù)目的類。使用較少的類,當(dāng)然使設(shè)計(jì)比較易于進(jìn)行。但是另一方面,由于使用裝飾器模式會(huì)產(chǎn)生比使用繼承關(guān)系更多的對象,更多的對象會(huì)使得查錯(cuò)變得困難,特別是這些對象看上去都很像。
裝飾器模式和適配器模式的區(qū)別
其實(shí)適配器模式也是一種包裝(Wrapper)模式,它們看似都是起到包裝一個(gè)類或?qū)ο蟮淖饔?#xff0c;但是它們使用的目的非常不一樣:
1、適配器模式的意義是要將一個(gè)接口轉(zhuǎn)變成另外一個(gè)接口,它的目的是通過改變接口來達(dá)到重復(fù)使用的目的
2、裝飾器模式不要改變被裝飾對象的接口,而是恰恰要保持原有的借口哦,但是增強(qiáng)原有接口的功能,或者改變元有對象的處理方法而提升性能
所以這兩種設(shè)計(jì)模式的目的是不同的。
總結(jié)
以上是生活随笔為你收集整理的java中装饰器_Java设计模式12:装饰器模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linuxu盘安装教程(linux u
- 下一篇: 用java代码写美国时间_如何衡量Jav