18、裝飾器模式(Decorator Pattern)

1. 裝飾器模式

1.1 簡(jiǎn)介

??Decorator模式就是在不改變?cè)愇募褪褂美^承的情況下,動(dòng)態(tài)的擴(kuò)展一個(gè)對(duì)象的功能。這些功能需要由用戶動(dòng)態(tài)決定加入的方式和時(shí)機(jī)墓猎,Decorator提供了"即插即用"的方法胡诗,在運(yùn)行期間決定何時(shí)增加何種功能邓线。

??通過創(chuàng)建一個(gè)包裝對(duì)象(裝飾)淌友,來包裹真實(shí)的對(duì)象。軟件開發(fā)的某個(gè)階段和裝修房子像極了骇陈!系統(tǒng)的基本功能實(shí)現(xiàn)后震庭,需要完善功能和修飾界面,但基本功能和流程框架不會(huì)做大的改動(dòng)你雌。房屋的裝修器联,是在毛胚房的基礎(chǔ)上層層 wrapper(包裝),先刷刷墻面漆婿崭,再鋪鋪木地板拨拓,再購置家具布置一下等,可以看作是對(duì)毛胚房層層包裝氓栈。所以Decorator 設(shè)計(jì)模式也被稱為 Wrapper 設(shè)計(jì)模式渣磷。

1.2

??Decorator 設(shè)計(jì)模式正如毛胚房的裝修,不會(huì)改變?cè)叻康幕究蚣懿疲皇窃黾有碌耐庥^幸海、功能等,且隨著時(shí)間的推移奥务,可以不斷的實(shí)施裝修工程:增加新的家具物独、根據(jù)心情換換新鮮的墻紙等等。在面向?qū)ο蟮某绦蛟O(shè)計(jì)中氯葬,擴(kuò)展系統(tǒng)的原有功能也可以采用繼承挡篓、組合的方式。繼承也不會(huì)改變毛胚房(父類)帚称,但是由于裝修工程的復(fù)雜和很多不可預(yù)測(cè)的改變官研,比如不同墻紙和地板樣式的組合數(shù)量簡(jiǎn)直無法想想,難道我們要為每一種組合都定義一個(gè)子類嗎闯睹?顯然這是不現(xiàn)實(shí)的戏羽,即通過繼承的方式來應(yīng)對(duì)未來的功能和外觀改變通常是吃力不討好的事情。組合的方式也不可取楼吃,因?yàn)檫@要求不斷的修改父類的結(jié)構(gòu)始花,相當(dāng)于對(duì)毛胚房大動(dòng)干戈,房屋的可維護(hù)性和可靠性就大大降低了孩锡。

??讓我們回顧一下設(shè)計(jì)模式的重要原則:Classes should be open for extenstion, but closed for modification酷宵。Decorator 設(shè)計(jì)模式很好的詮釋了這個(gè)原則。

1.3 Decorator模式結(jié)構(gòu)

Decorator模式uml:

Decorator模式uml.png

Decorator模式角色:

  • Component為統(tǒng)一接口躬窜,也是裝飾類和被裝飾類的基本類型浇垦。
  • ConcreteComponent為具體實(shí)現(xiàn)類,也是被裝飾類荣挨,他本身是個(gè)具有一些功能的完整的類男韧。
  • Decorator是裝飾類朴摊,實(shí)現(xiàn)了Component接口的同時(shí)還在內(nèi)部維護(hù)了一個(gè)ConcreteComponent的實(shí)例,并可以通過構(gòu)造函數(shù)初始化煌抒。
  • ConcreteDecorator是具體的裝飾產(chǎn)品類仍劈,每一種裝飾產(chǎn)品都具有特定的裝飾效果。

2. Decorator模式示例

??我們以毛胚房的裝修為例寡壮,我們可以在毛坯房的基礎(chǔ)上刷墻漆贩疙、鋪地板、安裝電器等等况既。

Component接口:

public interface Room {
 public String showRoom();
}

毛坯房 ConcreteComponent:

public class BlankRoom implements Room {
 
    @Override
 public String showRoom() {
 return "毛坯房";
 }
}

虛擬裝修器RoomDecorator:

// 裝修工程的模板
abstract public class RoomDecorator implements Room {
 // wrapper 的具體體現(xiàn)这溅,每個(gè)獨(dú)立的裝修工序都是在上一個(gè)裝修工序
 // 的基礎(chǔ)上進(jìn)行的,裝修就是這樣層層包裝完成的
 protected Room roomToBeDecorated;
 
 public RoomDecorator(Room roomToBeDecorated) {
 this.roomToBeDecorated = roomToBeDecorated;
 }
 
 @Override
 public String showRoom() {
 // 委托(delegate)
 return roomToBeDecorated.showRoom();
 }
}

PaintedDecorator:

 public PaintedDecorator(Room roomToBeDecorated) {
 super(roomToBeDecorated);
 }
 
 public String showRoom(){
 doPainting();
 return super.showRoom() + "刷墻漆"; 
 }
 
 // 刷墻漆
 private void doPainting(){}
 
}

FlooredDecorator:

public class FlooredDecorator extends RoomDecorator {
 public FlooredDecorator(Room roomToBeDecorated) {
 super(roomToBeDecorated);
 }
 
 
 public String showRoom(){
 doFlooring();
 return super.showRoom() + "鋪地板";
 }
 
 // 鋪地板
 private void doFlooring(){}
}

調(diào)用示例:

 public static void main(String[] args) {
 // 毛胚房
 Room blankRoom = new BlankRoom(); 
 
 // 刷了墻的毛胚房
 Room paintedRoom = new PaintedDecorator(new BlankRoom()); 
 
 // 先刷墻再鋪地板的毛胚房
 // 注意到連續(xù)的 new 操作棒仍,這就是 wrapper悲靴,最內(nèi)層的一般是毛胚房
 Room paintedAndFlooredRoom = new FlooredDecorator(new 
PaintedDecorator(new BlankRoom()));
 
 // 先鋪地板再刷墻的毛胚房
 Room flooredAndPaintedRoom = new PaintedDecorator(new 
FlooredDecorator(new BlankRoom()));
 
 System.out.println(blankRoom.showRoom());
 System.out.println(paintedRoom.showRoom());
 System.out.println(paintedAndFlooredRoom.showRoom());
 System.out.println(flooredAndPaintedRoom.showRoom());
 }

Decorator模式優(yōu)點(diǎn):

  • 裝飾器模式和繼承的共同特點(diǎn)就是擴(kuò)展對(duì)象的功能,而裝飾器模式比靜態(tài)繼承更加靈活.
  • 通過使用不同的具體裝飾器類莫其,及其不同的排列組合癞尚,可以產(chǎn)生出大量不同的組合.
  • 避免在層次結(jié)構(gòu)高層的類有太多的特征
  • Decorator與其他的Component不一樣,Decorator說一個(gè)透明的包裝乱陡。

Decorator模式缺點(diǎn):

  • 會(huì)出現(xiàn)一些小類(小程序)浇揩,過度使用會(huì)使程序變得復(fù)雜.

3. CDI 對(duì) Decorator模式的支持

??Decorator 設(shè)計(jì)模式雖然降低了需求變更對(duì)軟件開發(fā)的影響,但是通過層層包裝憨颠,即層層 new 操作創(chuàng)建對(duì)象的方式不夠優(yōu)雅胳徽。CDI 容器可以管理組件的生命周期,在大部分情況下我們無須通過 new 操作創(chuàng)建所需要的對(duì)象爽彤。CDI 中的 Decorator/Delegate 注解很大程度上簡(jiǎn)化了 Decorator 設(shè)計(jì)模式的代碼編寫量养盗,比如實(shí)現(xiàn)上面相同的功能,借助于 CDI适篙,就無須 RoomDecorator 這個(gè)抽象類了往核,所有的 Decorator 類直接實(shí)現(xiàn) Room 接口并使用注解聲明為 Decorator 即可,比如 PaintedDecorator 類:

** PaintedDecorator:**

import javax.decorator.Decorator;
import javax.decorator.Delegate;
import javax.inject.Inject;
 
@Decorator
public class PaintedDecorator implements Room {
 
 @Inject
 @Delegate
 Room roomToBeDecorated;
 
 public String showRoom(){
 doPainting();
 return roomToBeDecorated.showRoom() + "刷墻漆"; 
 }
 
 // 刷墻漆
 private void doPainting(){}
}

** RoomController 中是這樣使用 Decorator 類的:**

import javax.inject.Inject;
import javax.inject.Named;
 
@Named("room")
public class RoomController {
 
 @Inject
 Room room;
 
 public String showRoom(){
 return room.showRoom();
 }
}

??一切看起來簡(jiǎn)單多了嚷节,秘密就在于 CDI 的 beans.xml 文件铆铆,CDI 會(huì)根據(jù) beans.xml 文件中對(duì) Decorator 的聲明順序加載并構(gòu)造相應(yīng)的 Decorator 對(duì)象,完全復(fù)現(xiàn)了傳統(tǒng)方式的 Decorator 模式中通過 new 操作層層包裝“毛胚房”的過程丹喻。

beans.xml:

<?xml version="1.0" encoding="UTF-8"?>
 
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
 http://java.sun.com/xml/ns/javaee
 http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
 
 <!-- To activate CDI decorator, it must be specified below -->
 <decorators>
 <class>cn.edu.sdut.r314.PaintedDecorator</class>
 <class>cn.edu.sdut.r314.FlooredDecorator</class>
 </decorators>
</beans>

??完整的 CDI 版本的 Decorator 示例參見:https://github.com/subaochen/weld-tutorial,具體運(yùn)行方式參見其中的 README.md 文件翁都。

4. Decorator模式實(shí)際應(yīng)用

??在 Java IO API 中碍论,其實(shí)大量的使用了 Decorator 模式。試想一下柄慰,不同的輸入輸出源鳍悠,對(duì)輸入輸出數(shù)據(jù)的不同處理方式和不同流程税娜,Decorator 模式正是大顯身手的時(shí)候〔匮校基礎(chǔ)的輸入輸出類就好比是毛胚房敬矩,比如下面的用法:

BufferedInputStream bis = new BufferedInputStream(new FileInputStream("filename"));

??在Java IO 模式圖中可以更清楚的了解 Java IO 是如何使用 Decorator 模式的。
Java IO 模式圖:

Java IO 模式圖.png

??根據(jù)Java IO模式圖中的Decorator 模式蠢挡,我們可以實(shí)現(xiàn)一個(gè) Java IO 的 Decorator弧岳,比如讀入一個(gè)文件,將每個(gè)字母的 ASCII 碼都后移一位业踏,代碼如下:

** EncodeInputStream:**

public class EncodeInputStream extends FilterInputStream {
 protected EncodeInputStream(InputStream in) {
 super(in);
 } 
 
 public int read() throws IOException {
 int c = super.read();
 return c + 1;
 } 
}

** 調(diào)用示例:**

public class EncodeInputStream extends FilterInputStream {
public class InputTest {
 public static void main(String[] args) {
 int c;
 try {
 InputStream in = new EncodeInputStream(new 
BufferedInputStream(InputTest.class.getResourceAsStream("test.txt")));
 while ((c = in.read()) >= 0) {
 System.out.print((char) c); 
 }
 in.close();
 } catch (IOException e) {
 e.printStackTrace();
 }
 } 
}

??執(zhí)行后的輸出結(jié)果是(test.txt 文件的內(nèi)容是 hello,world!):ifmmp!xpsme"禽炬。Java IO 的 Decorator 在 CDI 環(huán)境下和傳統(tǒng)環(huán)境下寫法上沒有多大區(qū)別,只是一般在 CDI 環(huán)境下自己寫的 Decorator 可以當(dāng)作組件使用勤家,即可以通過 Decorator 的類型而不是名稱查詢組件而已腹尖,不再贅述。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末伐脖,一起剝皮案震驚了整個(gè)濱河市热幔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌讼庇,老刑警劉巖绎巨,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異巫俺,居然都是意外死亡认烁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門介汹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來却嗡,“玉大人,你說我怎么就攤上這事嘹承〈凹郏” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵叹卷,是天一觀的道長(zhǎng)撼港。 經(jīng)常有香客問我,道長(zhǎng)骤竹,這世上最難降的妖魔是什么帝牡? 我笑而不...
    開封第一講書人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蒙揣,結(jié)果婚禮上靶溜,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好罩息,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開白布嗤详。 她就那樣靜靜地躺著,像睡著了一般瓷炮。 火紅的嫁衣襯著肌膚如雪葱色。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評(píng)論 1 304
  • 那天娘香,我揣著相機(jī)與錄音苍狰,去河邊找鬼。 笑死茅主,一個(gè)胖子當(dāng)著我的面吹牛舞痰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播诀姚,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼响牛,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了赫段?” 一聲冷哼從身側(cè)響起呀打,我...
    開封第一講書人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎糯笙,沒想到半個(gè)月后贬丛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡给涕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年豺憔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片够庙。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡恭应,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出耘眨,到底是詐尸還是另有隱情昼榛,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布剔难,位于F島的核電站胆屿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏偶宫。R本人自食惡果不足惜非迹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望纯趋。 院中可真熱鬧彻秆,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽桦锄。三九已至扎附,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間结耀,已是汗流浹背留夜。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留图甜,地道東北人碍粥。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像黑毅,于是被迫代替她去往敵國和親嚼摩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容