Java设计模式12:装饰器模式
裝飾器模式
裝飾器模式又稱為包裝(Wrapper)模式。裝飾器模式以多客戶端透明的方式擴(kuò)展對(duì)象的功能,是繼承關(guān)系的一個(gè)替代方案。
?
裝飾器模式的結(jié)構(gòu)
通常給對(duì)象添加功能,要么直接修改對(duì)象添加相應(yīng)的功能,要么派生子類來(lái)擴(kuò)展,抑或是使用對(duì)象組合的方式。顯然,直接修改對(duì)應(yīng)的類的方式并不可取,在面向?qū)ο蟮脑O(shè)計(jì)中,我們應(yīng)該盡量使用組合對(duì)象而不是繼承對(duì)象來(lái)擴(kuò)展和復(fù)用功能,裝飾器模式就是基于對(duì)象組合的方式的。
裝飾器模式以對(duì)客戶端透明的方式動(dòng)態(tài)地給一個(gè)對(duì)象附加上了更多的責(zé)任。換言之,客戶端并不會(huì)角色對(duì)象在裝飾前和裝飾后有什么不同。裝飾器模式可以在不用創(chuàng)建更多子類的情況下,將對(duì)象的功能加以擴(kuò)展。
裝飾器模式中的角色有:
1、抽象構(gòu)件角色
給出一個(gè)抽象接口,以規(guī)范準(zhǔn)備接受附加責(zé)任的對(duì)象
2、具體構(gòu)件角色
定義一個(gè)將要接受附加責(zé)任的類
3、裝飾角色
持有一個(gè)構(gòu)建對(duì)象的實(shí)例,并定義一個(gè)與抽象構(gòu)件接口一致的接口
4、具體裝飾角色
負(fù)責(zé)給構(gòu)建對(duì)象貼上附加的責(zé)任
?
裝飾器模式的例子
現(xiàn)在有這么一個(gè)場(chǎng)景:
1、有一批廚師,簡(jiǎn)單點(diǎn)吧,就全是中國(guó)廚師,他們有一個(gè)共同的動(dòng)作是做晚飯
2、這批廚師做晚飯前的習(xí)慣不同,有些人喜歡做晚飯前洗手、有些人喜歡做晚飯前洗頭
那么,按照裝飾器模式,先抽象出抽象構(gòu)建角色,Cook接口:
public interface Cook {public void cookDinner();}具體構(gòu)建角色,中國(guó)廚師:
public class ChineseCook implements Cook {@Overridepublic void cookDinner() {System.out.println("中國(guó)人做晚飯");}}定義一個(gè)裝飾器角色,具體的工作具體裝飾器去實(shí)現(xiàn),這樣,比如美國(guó)廚師做晚飯前也先洗手或者先洗頭,這兩個(gè)動(dòng)作就可以做到復(fù)用,裝飾器角色定義為FilterCook,很簡(jiǎn)單,實(shí)現(xiàn)Cook接口并持有Cook的引用:
public abstract class FilterCook implements Cook {protected Cook cook;}最后定義一個(gè)具體裝飾角色,該洗手的洗手,該洗頭的洗頭:
public class WashHandsCook extends FilterCook {public WashHandsCook(Cook cook) {this.cook = cook;}@Overridepublic void cookDinner() {System.out.println("先洗手");cook.cookDinner();}} public class WashHearCook extends FilterCook {public WashHearCook(Cook cook) {this.cook = cook;}@Overridepublic void cookDinner() {System.out.println("先洗頭");cook.cookDinner();}}調(diào)用方這么實(shí)現(xiàn):
@Test public void testDecorate() {Cook cook0 = new WashHandsCook(new ChineseCook());Cook cook1 = new WashHearCook(new ChineseCook());cook0.cookDinner();cook1.cookDinner(); }運(yùn)行結(jié)果為:
先洗手 中國(guó)人做飯 先洗頭 中國(guó)人做飯簡(jiǎn)單的一個(gè)例子,實(shí)現(xiàn)了裝飾器模式的兩個(gè)功能點(diǎn):
這就是裝飾器模式。
?
裝飾器模式與Java字節(jié)輸入流InputStream
上面的例子可能寫(xiě)得不是很清楚,因此這里再繼續(xù)用代碼示例講解裝飾器模式。
裝飾器模式在Java體系中的經(jīng)典應(yīng)用是Java I/O,下面先講解字節(jié)輸入流InputStream,再講解字符輸入流Reader,希望可以通過(guò)這兩種輸入流的講解,加深對(duì)于裝飾器模式的理解。
首先看一下字節(jié)輸入流InputStream的類結(jié)構(gòu)體系:
InputStream是一個(gè)頂層的接口,文章開(kāi)頭就說(shuō),裝飾器模式是繼承關(guān)系的一種替代方案,看一下為什么:
這樣就導(dǎo)致兩個(gè)問(wèn)題:
所以,這個(gè)的時(shí)候我們就想到了一種解決方案:
這就是裝飾器模式簡(jiǎn)單的由來(lái),一切都是為了解決實(shí)際問(wèn)題而誕生。下一步,根據(jù)UML圖,我們來(lái)劃分一下裝飾器模式的角色。
1、InputStream是一個(gè)抽象構(gòu)件角色:
public abstract class InputStream implements Closeable {// SKIP_BUFFER_SIZE is used to determine the size of skipBufferprivate 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 extends InputStream {/* File Descriptor - handle to the open file */private FileDescriptor fd;private FileChannel channel = null;... }3、FilterInputStream無(wú)疑就是一個(gè)裝飾角色,因?yàn)镕ilterInputStream實(shí)現(xiàn)了InputStream內(nèi)的所有抽象方法并且持有一個(gè)InputStream的引用:
public class FilterInputStream extends InputStream {/*** The input stream to be filtered. */protected volatile InputStream in;... }4、具體裝飾角色就是InflaterInputStream、BufferedInputStream、DataInputStream,比如BufferedInputStream的聲明就是:
public class BufferedInputStream extends FilterInputStream {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 byte buf[];... }搞清楚具體角色之后,我們就可以這么寫(xiě)了:
public static void main(String[] args) throws Exception {File file = new File("D:/aaa.txt");InputStream in0 = new FileInputStream(file);InputStream in1 = new BufferedInputStream(new FileInputStream(file)); InputStream in2 = new DataInputStream(new BufferedInputStream(new FileInputStream(file))); }我們這里實(shí)例化出了三個(gè)InputStream的實(shí)現(xiàn)類:
同理,我要給ByteArrayInputStream、ObjectInputStream增加功能,也可以使用類似的方法,整個(gè)過(guò)程中,最重要的是要理解幾個(gè)問(wèn)題:
?
裝飾器模式與Java字符輸入流Reader
看完了上面的解讀,相信大家對(duì)于裝飾器模式應(yīng)當(dāng)有了一定的理解,那么再來(lái)看一下Java字符輸入流Reader,來(lái)加深對(duì)于裝飾器模式的印象。
簡(jiǎn)單看一下Reader的類體系結(jié)構(gòu):
根據(jù)UML,分析一下每個(gè)角色:
1、抽象構(gòu)建角色
毫無(wú)疑問(wèn),由Reader來(lái)扮演,它是一個(gè)抽象類,沒(méi)有具體功能
2、具體構(gòu)建角色
由InputStreamReader、CharArrayReader、PipedReader、StringReader來(lái)扮演
3、裝飾角色
由FilterReader來(lái)扮演,但是這里要提一下這個(gè)BufferedReader,它本身也可以作為裝飾角色出現(xiàn),看一下BufferedReader的繼承關(guān)系:
public class BufferedReader extends Reader {private Reader in;private char cb[];private int nChars, 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的引用。
?
半透明裝飾器模式與全透明裝飾器模式
再說(shuō)一下半透明裝飾器模式與全透明裝飾器模式,它們的區(qū)別是:
全透明裝飾器模式是一種比較理想主義的想法,現(xiàn)實(shí)中不太可能出現(xiàn)。
比如BufferedInputStream吧,我把FileInputStream裝飾為BufferedInputStream,難道BufferedInputStream就完全沒(méi)有自己的行為?比如返回緩沖區(qū)的大小、清空緩沖區(qū)(這里只是舉個(gè)例子,實(shí)際BufferedInputStream是沒(méi)有這兩個(gè)動(dòng)作的),這些都是InputStream本身不具備的,因?yàn)镮nputStream根本不知道緩沖區(qū)這個(gè)概念,它只知道定義讀數(shù)據(jù)相關(guān)方法。
所以,更多的我們是采用半透明的裝飾器模式,即允許裝飾后的類中有屬于自己的方法,因此,前面的I/O代碼示例可以這么改動(dòng):
public static void main(String[] args) throws Exception {File file = new File("D:/aaa.txt");FileInputStream in0 = new FileInputStream(file);BufferedInputStream in1 = new BufferedInputStream(new FileInputStream(file)); DataInputStream in2 = new DataInputStream(new BufferedInputStream(new FileInputStream(file))); }這樣才更有現(xiàn)實(shí)意義。
?
裝飾器模式的優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
1、裝飾器模式與繼承關(guān)系的目的都是要擴(kuò)展對(duì)象的功能,但是裝飾器模式可以提供比繼承更多的靈活性。裝飾器模式允許系統(tǒng)動(dòng)態(tài)決定貼上一個(gè)需要的裝飾,或者除掉一個(gè)不需要的裝飾。繼承關(guān)系是不同,繼承關(guān)系是靜態(tài)的,它在系統(tǒng)運(yùn)行前就決定了
2、通過(guò)使用不同的具體裝飾器以及這些裝飾類的排列組合,設(shè)計(jì)師可以創(chuàng)造出很多不同的行為組合
缺點(diǎn)
由于使用裝飾器模式,可以比使用繼承關(guān)系需要較少數(shù)目的類。使用較少的類,當(dāng)然使設(shè)計(jì)比較易于進(jìn)行。但是另一方面,由于使用裝飾器模式會(huì)產(chǎn)生比使用繼承關(guān)系更多的對(duì)象,更多的對(duì)象會(huì)使得查錯(cuò)變得困難,特別是這些對(duì)象看上去都很像。
?
裝飾器模式和適配器模式的區(qū)別
其實(shí)適配器模式也是一種包裝(Wrapper)模式,它們看似都是起到包裝一個(gè)類或?qū)ο蟮淖饔?#xff0c;但是它們使用的目的非常不一樣:
1、適配器模式的意義是要將一個(gè)接口轉(zhuǎn)變成另外一個(gè)接口,它的目的是通過(guò)改變接口來(lái)達(dá)到重復(fù)使用的目的
2、裝飾器模式不要改變被裝飾對(duì)象的接口,而是恰恰要保持原有的借口哦,但是增強(qiáng)原有接口的功能,或者改變?cè)袑?duì)象的處理方法而提升性能
所以這兩種設(shè)計(jì)模式的目的是不同的。
?
總結(jié)
以上是生活随笔為你收集整理的Java设计模式12:装饰器模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 蒋小华老师-中高层管理专家-专职助理冰冰
- 下一篇: java美元兑换,(Java实现) 美元