日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

Java设计模式12:装饰器模式

發(fā)布時(shí)間:2023/12/10 java 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java设计模式12:装饰器模式 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

裝飾器模式

裝飾器模式又稱為包裝(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):

  • 客戶端只定義了Cook接口,并不關(guān)心具體實(shí)現(xiàn)
  • 給Chinese增加上了洗頭和洗手的動(dòng)作,且洗頭和洗手的動(dòng)作,可以給其他國(guó)家的廚師類復(fù)用
  • 這就是裝飾器模式。

    ?

    裝飾器模式與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)系的一種替代方案,看一下為什么:

  • InputStream假設(shè)這里寫(xiě)了兩個(gè)實(shí)現(xiàn)類,FileInputStream,ObjectInputStream分別表示文件字節(jié)輸入流,對(duì)象字節(jié)輸入流
  • 現(xiàn)在我要給這兩個(gè)輸入流加入一點(diǎn)緩沖功能以提高輸入流效率,使用繼承的方式,那么就寫(xiě)一個(gè)BufferedInputStream,繼承FileInputStream,ObjectInputStream,給它們加功能
  • 現(xiàn)在我有另外一個(gè)需求,需要給這兩個(gè)輸入流加入一點(diǎn)網(wǎng)絡(luò)功能,那么就寫(xiě)一個(gè)SocketInputStream,繼承繼承FileInputStream,ObjectInputStream,給它們加功能
  • 這樣就導(dǎo)致兩個(gè)問(wèn)題:

  • 因?yàn)槲乙o哪個(gè)類加功能就必須繼承它,比如我要給FileInputStream,ObjectInputStream加上緩沖功能、網(wǎng)絡(luò)功能就得擴(kuò)展出2*2=4個(gè)類,更多的以此類推,這樣勢(shì)必導(dǎo)致類數(shù)量不斷膨脹
  • 代碼無(wú)法復(fù)用,給FileInputStream,ObjectInputStream加入緩沖功能,本身代碼應(yīng)該是一樣的,現(xiàn)在卻必須繼承完畢后把一樣的代碼重寫(xiě)一遍,多此一舉,代碼修改的時(shí)候必須修改多個(gè)地方,可維護(hù)性很差
  • 所以,這個(gè)的時(shí)候我們就想到了一種解決方案:

  • 在要擴(kuò)展的類比如BufferedInputStream中持有一個(gè)InputStream的引用,在BufferedInputStream調(diào)用InputStream中的方法,這樣擴(kuò)展的代碼就可以復(fù)用起來(lái)
  • 將BufferedInputStream作為InputStream的子類,這樣客戶端只知道我用的是InputStream而不需要關(guān)心具體實(shí)現(xiàn),可以在客戶端不知情的情況下,擴(kuò)展InputStream的功能,加上緩沖功能
  • 這就是裝飾器模式簡(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)類:

  • in0這個(gè)引用指向的是new出來(lái)的FileInputStream,這里簡(jiǎn)單構(gòu)造出了一個(gè)文件字節(jié)輸入流
  • in1這個(gè)引用指向的是new出來(lái)的BufferedInputStream,它給FileInputStream增加了緩沖功能,使得FileInputStream讀取文件的內(nèi)容保存在內(nèi)存中,以提高讀取的功能
  • in2這個(gè)引用指向的是new出來(lái)的DataInputStream,它也給FileInputStream增加了功能,因?yàn)樗蠨ataInputStream和BufferedInputStream兩個(gè)附加的功能
  • 同理,我要給ByteArrayInputStream、ObjectInputStream增加功能,也可以使用類似的方法,整個(gè)過(guò)程中,最重要的是要理解幾個(gè)問(wèn)題:

  • 哪些是具體構(gòu)建角色、哪些是具體裝飾角色,尤其是后者,區(qū)分的關(guān)鍵就是,角色中是否持有頂層接口的引用
  • 每個(gè)具體裝飾角色有什么作用,因?yàn)橹挥兄烂總€(gè)具體裝飾角色有什么作用后,才可以知道要裝飾某個(gè)功能需要用哪個(gè)具體裝飾角色
  • 使用構(gòu)造方法的方式將類進(jìn)行組合,給具體構(gòu)建角色加入新的功能
  • ?

    裝飾器模式與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ū)別是:

  • 對(duì)于半透明裝飾器模式,裝飾后的類未必有和抽象構(gòu)件角色同樣的接口方法,它可以有自己擴(kuò)展的方法
  • 對(duì)于全透明裝飾器模式,裝飾后的類有著和抽象構(gòu)件角色同樣的接口方法
  • 全透明裝飾器模式是一種比較理想主義的想法,現(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)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。