Java设计模式12:装饰器模式
裝飾器模式
裝飾器模式又稱為包裝(Wrapper)模式。裝飾器模式以多客戶端透明的方式擴展對象的功能,是繼承關系的一個替代方案。
?
裝飾器模式的結構
通常給對象添加功能,要么直接修改對象添加相應的功能,要么派生子類來擴展,抑或是使用對象組合的方式。顯然,直接修改對應的類的方式并不可取,在面向對象的設計中,我們應該盡量使用組合對象而不是繼承對象來擴展和復用功能,裝飾器模式就是基于對象組合的方式的。
裝飾器模式以對客戶端透明的方式動態地給一個對象附加上了更多的責任。換言之,客戶端并不會角色對象在裝飾前和裝飾后有什么不同。裝飾器模式可以在不用創建更多子類的情況下,將對象的功能加以擴展。
裝飾器模式中的角色有:
1、抽象構件角色
給出一個抽象接口,以規范準備接受附加責任的對象
2、具體構件角色
定義一個將要接受附加責任的類
3、裝飾角色
持有一個構建對象的實例,并定義一個與抽象構件接口一致的接口
4、具體裝飾角色
負責給構建對象貼上附加的責任
?
裝飾器模式的例子
現在有這么一個場景:
1、有一批廚師,簡單點吧,就全是中國廚師,他們有一個共同的動作是做晚飯
2、這批廚師做晚飯前的習慣不同,有些人喜歡做晚飯前洗手、有些人喜歡做晚飯前洗頭
那么,按照裝飾器模式,先抽象出抽象構建角色,Cook接口:
public interface Cook {public void cookDinner();}具體構建角色,中國廚師:
public class ChineseCook implements Cook {@Overridepublic void cookDinner() {System.out.println("中國人做晚飯");}}定義一個裝飾器角色,具體的工作具體裝飾器去實現,這樣,比如美國廚師做晚飯前也先洗手或者先洗頭,這兩個動作就可以做到復用,裝飾器角色定義為FilterCook,很簡單,實現Cook接口并持有Cook的引用:
public abstract class FilterCook implements Cook {protected Cook cook;}最后定義一個具體裝飾角色,該洗手的洗手,該洗頭的洗頭:
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();}}調用方這么實現:
@Test public void testDecorate() {Cook cook0 = new WashHandsCook(new ChineseCook());Cook cook1 = new WashHearCook(new ChineseCook());cook0.cookDinner();cook1.cookDinner(); }運行結果為:
先洗手 中國人做飯 先洗頭 中國人做飯簡單的一個例子,實現了裝飾器模式的兩個功能點:
這就是裝飾器模式。
?
裝飾器模式與Java字節輸入流InputStream
上面的例子可能寫得不是很清楚,因此這里再繼續用代碼示例講解裝飾器模式。
裝飾器模式在Java體系中的經典應用是Java I/O,下面先講解字節輸入流InputStream,再講解字符輸入流Reader,希望可以通過這兩種輸入流的講解,加深對于裝飾器模式的理解。
首先看一下字節輸入流InputStream的類結構體系:
InputStream是一個頂層的接口,文章開頭就說,裝飾器模式是繼承關系的一種替代方案,看一下為什么:
這樣就導致兩個問題:
所以,這個的時候我們就想到了一種解決方案:
這就是裝飾器模式簡單的由來,一切都是為了解決實際問題而誕生。下一步,根據UML圖,我們來劃分一下裝飾器模式的角色。
1、InputStream是一個抽象構件角色:
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都是具體構建角色,比如FileInputStream,它的聲明是:
public class FileInputStream extends InputStream {/* File Descriptor - handle to the open file */private FileDescriptor fd;private FileChannel channel = null;... }3、FilterInputStream無疑就是一個裝飾角色,因為FilterInputStream實現了InputStream內的所有抽象方法并且持有一個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[];... }搞清楚具體角色之后,我們就可以這么寫了:
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))); }我們這里實例化出了三個InputStream的實現類:
同理,我要給ByteArrayInputStream、ObjectInputStream增加功能,也可以使用類似的方法,整個過程中,最重要的是要理解幾個問題:
?
裝飾器模式與Java字符輸入流Reader
看完了上面的解讀,相信大家對于裝飾器模式應當有了一定的理解,那么再來看一下Java字符輸入流Reader,來加深對于裝飾器模式的印象。
簡單看一下Reader的類體系結構:
根據UML,分析一下每個角色:
1、抽象構建角色
毫無疑問,由Reader來扮演,它是一個抽象類,沒有具體功能
2、具體構建角色
由InputStreamReader、CharArrayReader、PipedReader、StringReader來扮演
3、裝飾角色
由FilterReader來扮演,但是這里要提一下這個BufferedReader,它本身也可以作為裝飾角色出現,看一下BufferedReader的繼承關系:
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是可以被認為是一個裝飾角色的。
4、具體裝飾角色
BufferedReader上面提到了扮演了裝飾角色,但是也可以被認為是一個具體裝飾角色。除了BufferedReader,具體裝飾角色還有PushbackReader。FileReader盡管也在第三行,但是FileReader構不成一個具體裝飾角色,因為它不是BufferedReader的子類也不是FilterReader的子類,不持有Reader的引用。
?
半透明裝飾器模式與全透明裝飾器模式
再說一下半透明裝飾器模式與全透明裝飾器模式,它們的區別是:
全透明裝飾器模式是一種比較理想主義的想法,現實中不太可能出現。
比如BufferedInputStream吧,我把FileInputStream裝飾為BufferedInputStream,難道BufferedInputStream就完全沒有自己的行為?比如返回緩沖區的大小、清空緩沖區(這里只是舉個例子,實際BufferedInputStream是沒有這兩個動作的),這些都是InputStream本身不具備的,因為InputStream根本不知道緩沖區這個概念,它只知道定義讀數據相關方法。
所以,更多的我們是采用半透明的裝飾器模式,即允許裝飾后的類中有屬于自己的方法,因此,前面的I/O代碼示例可以這么改動:
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))); }這樣才更有現實意義。
?
裝飾器模式的優缺點
優點
1、裝飾器模式與繼承關系的目的都是要擴展對象的功能,但是裝飾器模式可以提供比繼承更多的靈活性。裝飾器模式允許系統動態決定貼上一個需要的裝飾,或者除掉一個不需要的裝飾。繼承關系是不同,繼承關系是靜態的,它在系統運行前就決定了
2、通過使用不同的具體裝飾器以及這些裝飾類的排列組合,設計師可以創造出很多不同的行為組合
缺點
由于使用裝飾器模式,可以比使用繼承關系需要較少數目的類。使用較少的類,當然使設計比較易于進行。但是另一方面,由于使用裝飾器模式會產生比使用繼承關系更多的對象,更多的對象會使得查錯變得困難,特別是這些對象看上去都很像。
?
裝飾器模式和適配器模式的區別
其實適配器模式也是一種包裝(Wrapper)模式,它們看似都是起到包裝一個類或對象的作用,但是它們使用的目的非常不一樣:
1、適配器模式的意義是要將一個接口轉變成另外一個接口,它的目的是通過改變接口來達到重復使用的目的
2、裝飾器模式不要改變被裝飾對象的接口,而是恰恰要保持原有的借口哦,但是增強原有接口的功能,或者改變元有對象的處理方法而提升性能
所以這兩種設計模式的目的是不同的。
?
總結
以上是生活随笔為你收集整理的Java设计模式12:装饰器模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 蒋小华老师-中高层管理专家-专职助理冰冰
- 下一篇: 【小技巧】【map】【set】【Java