設(shè)計(jì)模式之裝飾者模式(Decorator Pattern)

裝飾者模式可以做到在不修改任何底層代碼的情況下叙身,給對(duì)象增加的新的方法芜茵。
首先夷都,我們通過(guò)對(duì)一個(gè)現(xiàn)實(shí)問(wèn)題的模擬分析,了解什么是裝飾者模式以及裝飾者模式的作用尤莺。


問(wèn)題提出

咖啡店在街頭隨處可見(jiàn)旅敷。我們以咖啡店的飲品訂單系統(tǒng)為例。假設(shè)我們要設(shè)計(jì)一個(gè)飲品的訂單系統(tǒng)颤霎。
設(shè)計(jì)了一個(gè)這樣的類(lèi)圖:

Paste_Image.png

Beverage是一個(gè) 抽象類(lèi)媳谁,所有咖啡店的飲品都必須繼承這個(gè)類(lèi),description是飲品的描述信息友酱,cost()是計(jì)算此種飲品的價(jià)格晴音。

我們會(huì)遇到這樣的問(wèn)題,在購(gòu)買(mǎi)飲品的時(shí)候缔杉,我們可以要求在其中加入不同的調(diào)料配品锤躁,比如,摩卡(Mocha)或详,加奶泡等进苍。除了原本飲料需要的價(jià)格的外,咖啡店會(huì)根據(jù)所加入調(diào)料的再收取不同的費(fèi)用鸭叙。

如果按照之前的設(shè)計(jì)方式觉啊,那么會(huì)出現(xiàn)如下的情況:

Paste_Image.png

** 顯然這似乎已經(jīng)是類(lèi)爆炸了!**

而且我們永遠(yuǎn)無(wú)法預(yù)測(cè)沈贝,顧客會(huì)選取怎樣的調(diào)料的搭配杠人,每當(dāng)出現(xiàn)一個(gè)新的調(diào)料搭配時(shí),我們就需要增加一個(gè)新的類(lèi)宋下。
更加糟糕的是嗡善,當(dāng)原料配料的價(jià)格上漲后或者下降后,那么所有涉及到這種配料的類(lèi)都得重新改過(guò)学歧。這簡(jiǎn)直是個(gè)噩夢(mèng)罩引!很顯然這很不符合我們?cè)O(shè)計(jì)模式的原則。作為一個(gè)程序員枝笨,我們是決不能容忍這種情況發(fā)生的袁铐!

那么我們?cè)撊绾卧O(shè)計(jì)呢揭蜒?

這里就需要用到我們的裝飾者模式!

引出裝飾者模式

讓我們轉(zhuǎn)換思路剔桨,我們以飲品beverage為主體屉更,在運(yùn)行時(shí)以顧客選擇的調(diào)料來(lái)裝飾beverage。比如洒缀,如果顧客想要摩卡和奶泡的拿鐵咖啡瑰谜,我們要做的應(yīng)該是這樣的:

  • 取一個(gè)拿鐵咖啡的對(duì)象
Paste_Image.png
  • 用摩卡對(duì)象裝飾它
Paste_Image.png
  • 用奶泡對(duì)象裝飾它
Paste_Image.png
  • 調(diào)用cost方法計(jì)算價(jià)錢(qián),并依賴(lài)委托將配料摩卡和奶泡加上去树绩。
Paste_Image.png

會(huì)先計(jì)算whip的cost然后調(diào)用mocha的cost萨脑,然后調(diào)用拿鐵的cost,這樣就計(jì)算出了總價(jià)格饺饭。
這樣就是實(shí)現(xiàn)的裝飾者模式解決這個(gè)問(wèn)題的思路砚哗。
下面我們看一下裝飾者模式的定義,以及代碼實(shí)現(xiàn)的基本思路

定義裝飾者模式

裝飾者模式動(dòng)態(tài)的將責(zé)任附加到對(duì)象上砰奕。若要擴(kuò)展功能蛛芥,裝飾者提供了比繼承更有彈性的替代方案。

Paste_Image.png

這個(gè)類(lèi)圖就是裝飾者模式的實(shí)現(xiàn)方式军援。更詳細(xì)的是如下這個(gè)版本的類(lèi)圖仅淑。

Paste_Image.png

下面我們就根據(jù)這個(gè)類(lèi)圖來(lái)解決我們之前在實(shí)現(xiàn)咖啡店飲料系統(tǒng)上遇到的問(wèn)題。

Paste_Image.png

分析設(shè)計(jì)類(lèi)圖:

  • beverage相當(dāng)于抽象的component類(lèi)胸哥,具體的component和decorator都需要繼承實(shí)現(xiàn)這個(gè)抽象類(lèi)涯竟。
  • 四個(gè)具體的飲料的類(lèi),相當(dāng)于concrete component空厌!每一個(gè)類(lèi)代表了一個(gè)飲料類(lèi)型庐船。
  • condimentDecorator是抽象的decorator類(lèi),它是所有調(diào)料類(lèi)的抽象嘲更,它保存了beverage的一個(gè)引用筐钟。
  • 調(diào)料裝飾者類(lèi)繼承自condimentDecorator,是各種具體調(diào)料的實(shí)現(xiàn)赋朦,他們都實(shí)現(xiàn)了cost方法篓冲。

上面有一個(gè)非常關(guān)鍵的地方,就是我們注意到裝飾者和被裝飾者必須是一樣的類(lèi)型宠哄,也就是擁有共同的超類(lèi)壹将。這樣做是因?yàn)槲覀円b飾者必須能取代被裝飾者。
這樣我們就可以利用對(duì)象的組合毛嫉,將調(diào)料和飲料的行為組合起來(lái)诽俯。這符合我們之前提到的設(shè)計(jì)原則多用組合,少用繼承

實(shí)現(xiàn)裝飾者模式

如果看到這里還是不太清楚承粤,也沒(méi)關(guān)系暴区,接下來(lái)我們將具體實(shí)現(xiàn)代碼闯团,對(duì)裝飾者模式有一個(gè)直觀(guān)根本的了解。

  • 首先實(shí)現(xiàn)beverage和condiment兩個(gè)抽象類(lèi)
package abstractComponent;

public abstract class Beverage {
    protected String description = "Unknow Beverage";
    
    public String getDescription() {
        return description;
    }
    
    public abstract double cost();
}
package abstractDecrator;
import abstractComponent.Beverage;


public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}
  • 然后我們實(shí)現(xiàn)具體的飲料類(lèi)
package concreteComponent;
import abstractComponent.Beverage;


public class Coco extends Beverage {
    public Coco(){
        description = "Coco";
    }
    
    public double cost(){
        return 0.89;
    }
}
package concreteComponent;
import abstractComponent.Beverage;


public class Espresso extends Beverage {
    public Espresso() {
        description = "Espresso";
    }
    
    public double cost() {
        return 1.99;
    }
}
  • 我們?cè)賹?shí)現(xiàn)具體的裝飾者類(lèi)颜启,也就是調(diào)料類(lèi)
package concreteDecorator;

import abstractComponent.Beverage;
import abstractDecrator.CondimentDecorator;

public class Mocha extends CondimentDecorator {
    
    Beverage beverage;
    
    public Mocha(Beverage beverage){
        this.beverage = beverage;
    }
    
    
    @Override
    public double cost() {
        // TODO Auto-generated method stub
        return .20 + beverage.cost();
    }

    @Override
    public String getDescription() {
        // TODO Auto-generated method stub
        return beverage.getDescription() + ", Mocha";
    }

}
package concreteDecorator;

import abstractComponent.Beverage;
import abstractDecrator.CondimentDecorator;

public class Soy extends CondimentDecorator {
    
    Beverage beverage;
    
    public Soy(Beverage beverage){
        this.beverage = beverage;
    }
    
    @Override
    public String getDescription() {
        // TODO Auto-generated method stub
        return beverage.getDescription() + ", Soy";
    }

    @Override
    public double cost() {
        // TODO Auto-generated method stub
        return .15 + beverage.cost();
    }

}
package concreteDecorator;

import abstractComponent.Beverage;
import abstractDecrator.CondimentDecorator;

public class Whip extends CondimentDecorator {
    
    Beverage beverage;
    
    public Whip(Beverage beverage){
        this.beverage = beverage;
    }
    
    @Override
    public String getDescription() {
        // TODO Auto-generated method stub
        return beverage.getDescription() + " , whip";
    }

    @Override
    public double cost() {
        // TODO Auto-generated method stub
        return .10 + beverage.cost();
    }

}
  • 最后編寫(xiě)一個(gè)測(cè)試類(lèi),來(lái)測(cè)試我們裝飾者模式的效果如何
import concreteComponent.Coco;
import concreteComponent.Espresso;
import concreteDecorator.Mocha;
import concreteDecorator.Soy;
import concreteDecorator.Whip;
import abstractComponent.Beverage;


public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Beverage beverage = new Espresso();
        System.out.println( beverage.getDescription() + "$" + beverage.cost());
        
        Beverage beverage2 = new Coco();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println( beverage2.getDescription() + "$" + beverage2.cost());
        
        Beverage beverage3 = new Espresso();
        beverage3 = new Whip(beverage3);
        beverage3 = new Mocha(beverage3);
        beverage3 = new Soy(beverage3);
        System.out.println( beverage3.getDescription() + "$" + beverage3.cost());
    }

}

總結(jié)與分析

通過(guò)裝飾者模式我們可以很好的解決咖啡店的問(wèn)題浪讳,用裝飾者去包裝組件缰盏,可以達(dá)到很好的可擴(kuò)展性。

  • 裝飾者模式用到的技術(shù)主要有兩種就是組合和委托淹遵,這幫助我們動(dòng)態(tài)的在運(yùn)行時(shí)加上新的行為口猜。
  • 裝飾者模式意味著一群裝飾者類(lèi),這些類(lèi)用來(lái)包裝裝飾者透揣。
  • 裝飾者和被裝飾者類(lèi)實(shí)際上具有相同類(lèi)型的济炎。
  • 裝飾者可以在被裝飾者的行為前面或后面加上自己的行為,甚至完全覆蓋辐真。
  • 但裝飾者模式的使用會(huì)導(dǎo)致出現(xiàn)很多小對(duì)象须尚,就是裝飾者對(duì)象,過(guò)度使用也會(huì)使程序變得復(fù)雜侍咱。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末耐床,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子楔脯,更是在濱河造成了極大的恐慌撩轰,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昧廷,死亡現(xiàn)場(chǎng)離奇詭異堪嫂,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)木柬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)皆串,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人眉枕,你說(shuō)我怎么就攤上這事愚战。” “怎么了齐遵?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵寂玲,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我梗摇,道長(zhǎng)拓哟,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任伶授,我火速辦了婚禮断序,結(jié)果婚禮上流纹,老公的妹妹穿的比我還像新娘。我一直安慰自己违诗,他們只是感情好漱凝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著诸迟,像睡著了一般茸炒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上阵苇,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天壁公,我揣著相機(jī)與錄音,去河邊找鬼绅项。 笑死紊册,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的快耿。 我是一名探鬼主播囊陡,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼掀亥!你這毒婦竟也來(lái)了关斜?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤铺浇,失蹤者是張志新(化名)和其女友劉穎痢畜,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體鳍侣,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡丁稀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了倚聚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片线衫。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖惑折,靈堂內(nèi)的尸體忽然破棺而出授账,到底是詐尸還是另有隱情,我是刑警寧澤惨驶,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布白热,位于F島的核電站,受9級(jí)特大地震影響粗卜,放射性物質(zhì)發(fā)生泄漏屋确。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望攻臀。 院中可真熱鬧焕数,春花似錦、人聲如沸刨啸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)设联。三九已至善已,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間仑荐,已是汗流浹背雕拼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工纵东, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留粘招,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓偎球,卻偏偏與公主長(zhǎng)得像洒扎,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子衰絮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

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

  • 引言 在介紹裝飾者模式之前袍冷,我們先了解一個(gè)設(shè)計(jì)原則: 多用組合,少用繼承猫牡。 在平時(shí)寫(xiě)代碼時(shí)胡诗,我們應(yīng)該減少類(lèi)繼承的使...
    Zentopia閱讀 4,101評(píng)論 4 11
  • 《Head First設(shè)計(jì)模式》讀書(shū)筆記 裝飾者模式 一,場(chǎng)景介紹 1淌友,需求 一杯主飲料(Beverage)需要加...
    呆麻子閱讀 3,412評(píng)論 0 10
  • 設(shè)計(jì)模式之裝飾者模式 需求場(chǎng)景 咖啡店訂單系統(tǒng) 需求分析 咖啡種類(lèi)比較多煌恢,結(jié)賬的時(shí)候我們需要知道咖啡名和價(jià)格。首先...
    納薩立克閱讀 603評(píng)論 2 13
  • 距離國(guó)慶+中秋的8天小長(zhǎng)假還有2天就要來(lái)了, 炒雞雞凍有木有~ 想好這8天怎么過(guò)了嗎器联, 是出門(mén)浪還是準(zhǔn)備躺尸8天二汛?...
    陳軒007閱讀 362評(píng)論 0 0
  • No.65 晚上濃重的霧,讓我看不清綿延的路拨拓。遠(yuǎn)處的霓虹燈閃著光肴颊,成了唯一的點(diǎn)綴,比星光離我更近渣磷,比時(shí)光離我更遠(yuǎn)苫昌。...
    水淺_bling閱讀 1,057評(píng)論 2 11