【阿里大神講設(shè)計(jì)模式】5.可樂(lè)要加冰才好喝啊---裝飾模式

本系列文章由阿里大神 **anly_jun **授權(quán)發(fā)布

前情提要

上集講到, 小光利用策略模式搞起了回饋顧客的活動(dòng). 還別說(shuō), 客流量增大不少.

然而, 隨之而來(lái)的, 顧客的聲音也不少:

  1. 可樂(lè)能不能加冰啊
  1. 綠豆湯加點(diǎn)糖唄
    ......

眾口難調(diào)嘛, 大家的需求不一, 有的要冰有的不要, 有的加糖有的不要... 小光帶著客戶的意見(jiàn), 開(kāi)始了飲品的改進(jìn)之路.

改進(jìn)之路

第一套方案

很快, 小光想出了第一套的解決方案: 我把加冰和不加冰的的飲料看成是兩種不同的飲料, 借助上次設(shè)計(jì)的工廠方法模式的飲料機(jī), 可以很輕易的擴(kuò)展實(shí)現(xiàn)用戶不同需求, 而不修改原來(lái)的實(shí)現(xiàn)啊.

小光的第一套方案:

然而, 還沒(méi)有投入使用呢, 小光就發(fā)現(xiàn)了問(wèn)題:

我這不定期會(huì)從飲料商戶那兒拿到一些不同的飲品, 每種都有可能要加冰, 加糖, 還可能加蜂蜜呢...那我不用干別的啦, 天天折騰這些飲料機(jī)就夠夠的啦.

裝飾出場(chǎng)

那么有什么更好的辦法呢? 小光又陷入了冥想.
這時(shí), 表妹過(guò)來(lái)了, 興高采烈的, 看到小光傻傻的樣子, 問(wèn): "表哥, 你想啥呢, 看看我剛新買的頭花好看不?"
小光: "你怎么又換頭花了啊?"
表妹: "女孩子嘛, 當(dāng)然要換著花樣打扮裝飾自己啊, 好不好看嘛?"
"好看好看." 小光敷衍答道.

突然, 小光像是想起了什么, 喃喃自語(yǔ)道, "打扮", "裝飾", "對(duì)啊, 我也可以用冰啊, 糖啊, 蜂蜜什么的來(lái)裝飾我的飲料啊."

說(shuō)做就做, 小光先從可樂(lè)入手:

良好的面向接口編程習(xí)慣, 小光抽象了一個(gè)飲料類Drink

public interface Drink {
    String make();
}

實(shí)現(xiàn)一杯可樂(lè):

public class Coke implements Drink {
    @Override
    public String make() {
        return "這是一杯可樂(lè)";
    }
}

加了冰塊的飲料還應(yīng)該是飲料, 故而, 冰塊這個(gè)裝飾也實(shí)現(xiàn)了Drink接口, 加冰這個(gè)裝飾是在飲料的基礎(chǔ)上的, 所以我們還持有了一個(gè)Drink對(duì)象:

public class Ice implements Drink {

    private Drink originalDrink;
    public Ice(Drink originalDrink) {
        this.originalDrink = originalDrink;
    }
    @Override
    public String make() {
        return originalDrink.make() + ", 加一塊冰";
    }
}

來(lái)實(shí)驗(yàn)下:

public class XiaoGuang {

    public static void main(String[] args) {
        Drink coke = new Coke();
        System.out.println(coke.make());
        Drink iceCoke = new Ice(new Coke());
        System.out.println(iceCoke.make());
    }
}

結(jié)果:

這是一杯可樂(lè)
這是一杯可樂(lè), 加一塊冰

持續(xù)改進(jìn)

小光看到實(shí)驗(yàn)結(jié)果, 哈哈大笑, 實(shí)驗(yàn)成功, 且看我推廣到所有飲料, 所有加料中.

小光想到, 加料可能是糖, 可能是冰, 還可能蜂蜜, 還有很多未知的, 為了滿足開(kāi)閉原則, 我還是先抽象一個(gè)配料出來(lái)吧:

public abstract class Stuff implements Drink {

    private Drink originalDrink;
    public Stuff(Drink originalDrink) {
        this.originalDrink = originalDrink;
    }

    @Override
    public String make() {
        return originalDrink.make() + ", 加一份" + stuffName();
    }

    abstract String stuffName();
}

冰塊原料改為繼承Stuff:

public class Ice extends Stuff {
    public Ice(Drink originalDrink) {
        super(originalDrink);
    }
    @Override
    String stuffName() {
        return "冰";
    }
}

小光還據(jù)此增加了糖Sugar, 和蜂蜜Honey原料.

具體代碼就不在此貼出來(lái)了, 全部代碼在這里.

投入使用

經(jīng)過(guò)大量驗(yàn)證后, 小光將新的程序投入了使用, 現(xiàn)在是這樣子的...

"老板, 來(lái)一杯可樂(lè), 加冰"

Drink iceCoke = new Ice(new Coke());
System.out.println(iceCoke.make());
這是一杯可樂(lè), 加一份冰

"老板, X飲料, 加冰, 加糖"

Drink iceSugarXDrink = new Ice(new Sugar(new XDrink()));
System.out.println(iceSugarXDrink.make());
這是一杯X牌飲料, 加一份糖, 加一份冰

"可樂(lè), 加兩份冰, 加蜂蜜"

Drink doubleIceHoneyCoke = new Ice(new Ice(new Honey(new Coke())));
System.out.println(doubleIceHoneyCoke.make());
這是一杯可樂(lè), 加一份蜂蜜, 加一份冰, 加一份冰

完美表現(xiàn).
小光再也不怕顧客們的各種奇怪的加料要求了.

故事之后

照例, 故事之后, 我們用UML類圖來(lái)梳理下上述的關(guān)系:


其中Stuff類中值得注意的兩個(gè)關(guān)系:

我們的Stuff(料)也是實(shí)現(xiàn)了Drink接口的, 這是為了說(shuō)明加了料(Stuff)的飲料還是飲料.
Stuff中還聚合了一個(gè)Drink(originalDrink)實(shí)例, 是為了說(shuō)明這個(gè)料是加到飲料中的.
對(duì), 這個(gè)就是一個(gè)標(biāo)準(zhǔn)的裝飾者模式的類圖.

裝飾者模式就是用來(lái)動(dòng)態(tài)的給對(duì)象加上額外的職責(zé).
Drink是被裝飾的對(duì)象, Stuff作為裝飾類, 可以動(dòng)態(tài)地給被裝飾對(duì)象添加特征, 職責(zé).

擴(kuò)展閱讀一

有的同學(xué)可能會(huì)說(shuō), 我完全可以通過(guò)繼承關(guān)系, 在子類中添加職責(zé)的方式給父類以擴(kuò)展啊. 是的, 沒(méi)錯(cuò), 繼承本就是為了擴(kuò)展.

然而, 裝飾者模式和子類繼承擴(kuò)展的最大區(qū)別在于:

裝飾者模式強(qiáng)調(diào)的是動(dòng)態(tài)的擴(kuò)展, 而繼承關(guān)系是靜態(tài)的.

由于繼承機(jī)制的靜態(tài)性, 我們會(huì)為每個(gè)擴(kuò)展職責(zé)創(chuàng)建一個(gè)子類, 例如IceCoke, DoubleIceCoke, SugarXDrink, IceSugarXDrink等等...會(huì)造成類爆炸.

另外, 這里引入一條新的面向?qū)ο缶幊淘瓌t:
組合優(yōu)于繼承, 大家自行體會(huì)下.

擴(kuò)展閱讀二

還有的同學(xué)說(shuō), 這種按需定制的方式貌似跟之前講的Builder模式有點(diǎn)像啊, 那為什么不用Builder模式呢.

這里先說(shuō)明下二者的本質(zhì)差異:

Builder模式是一種創(chuàng)建型的設(shè)計(jì)模式. 旨在解決對(duì)象的差異化構(gòu)建的問(wèn)題.
裝飾者模式是一種結(jié)構(gòu)型的設(shè)計(jì)模式. 旨在處理對(duì)象和類的組合關(guān)系.

實(shí)際上在這個(gè)例子中, 我們是可以用Builder模式的, 但就像使用繼承機(jī)制一樣, 會(huì)有些問(wèn)題.
首先, Builder模式是構(gòu)建對(duì)象, 那么實(shí)際上要求我們是必須事先了解有哪些屬性/職責(zé)供選擇. 這樣我們才可以在構(gòu)建對(duì)象時(shí)選擇不同的Build方式. 也就是說(shuō):

Builder模式的差異化構(gòu)建是可預(yù)見(jiàn)的, 而裝飾者模式實(shí)際上提供了一種不可預(yù)見(jiàn)的擴(kuò)展組合關(guān)系.

例如, 如果用戶要了一杯帶蜂蜜的X飲料, 如果是Builder模式, 我們可能是這樣的:

XDrink xDrink = new XDrink.Builder()
        .withHoney()
        .build()

但是如果飲料拿出來(lái)后, 用戶還想加冰怎么辦呢? 這就是Builder模式在這種動(dòng)態(tài)擴(kuò)展情景下的局限. 看看裝飾模式怎么做:

// 加蜂蜜的X飲料
Drink honeyXDrink = new Honey(new XDrink());
System.out.println(honeyXDrink.make());

這是一杯X牌飲料, 加一份蜂蜜

// 還要加冰
Drink iceHoneyXDrink = new Ice(honeyXDrink);
System.out.println(iceHoneyXDrink.make());

這是一杯X牌飲料, 加一份蜂蜜, 加一份冰

直接在honeyXDrink的基礎(chǔ)上再裝飾一份冰即可.

另外, 大家也都看到了, 由于我們的面向接口編程方式, 裝飾者(冰塊, 蜂蜜, 糖)可不是只能用來(lái)裝飾特定的被裝飾對(duì)象(諸如可樂(lè)), 它們可以被用來(lái)裝飾所有種類的飲料(Drink)對(duì)象. 個(gè)中好處大家自行體會(huì)下.

擴(kuò)展閱讀三

我們一直在說(shuō)的, 裝飾者模式是動(dòng)態(tài)給對(duì)象加上額外的職責(zé), 這個(gè)職責(zé)實(shí)際上包括修飾(屬性), 也包括行為(方法).

例如我們上面討論的例子, 就是單純得給飲料加上了一些修飾, 是飲料編程了加冰的飲料, 加糖的飲料. 我們也可以給對(duì)象加上行為, 例如, 一部手機(jī), 我們可以通過(guò)裝飾模式給它加上紅外遙控的功能, 還可以給他加上NFC支付的功能.

Android中鼎鼎大名的Context體系, 實(shí)際上就是裝飾模式的體現(xiàn), 通過(guò)裝飾模式來(lái)給Context擴(kuò)展了一系列的功能. 如下:


還是比較清晰的, 典型的使用裝飾者模式來(lái)擴(kuò)展功能的實(shí)踐, 相比于我們的例子, 更注重功能擴(kuò)展, 體現(xiàn)了開(kāi)閉原則.

再重申下, 面向?qū)ο缶幊淌且环N思想, 設(shè)計(jì)模式都是這些思想的具體實(shí)踐和體現(xiàn). 學(xué)習(xí)設(shè)計(jì)模式可以讓我們更好的理解編程思想, 而非固定套用. 我們的目的是無(wú)招勝有招.

另外, 具體的環(huán)境中設(shè)計(jì)模式一般不是單一地用的, 往往是各種設(shè)計(jì)模式融合在一起的. 例如我們這個(gè)裝飾者里面, 各種new對(duì)象, 實(shí)際上就可以用到我們學(xué)習(xí)的工廠方法的模式進(jìn)行處理.

好了, 讓我們充分發(fā)揮自己的想象力, 多弄些原料來(lái)裝飾我們的飲料吧, 說(shuō)不定我們能調(diào)出一種前所未有的飲品呢, 哈哈.

140套Android優(yōu)秀開(kāi)源項(xiàng)目源碼狭姨,領(lǐng)取地址:http://mp.weixin.qq.com/s/afPGHqfdiApALZqHsXbw-A

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市取募,隨后出現(xiàn)的幾起案子叠国,更是在濱河造成了極大的恐慌,老刑警劉巖蕾总,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粥航,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡生百,警方通過(guò)查閱死者的電腦和手機(jī)递雀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蚀浆,“玉大人缀程,你說(shuō)我怎么就攤上這事∈锌。” “怎么了杨凑?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)摆昧。 經(jīng)常有香客問(wèn)我撩满,道長(zhǎng),這世上最難降的妖魔是什么绅你? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任伺帘,我火速辦了婚禮,結(jié)果婚禮上忌锯,老公的妹妹穿的比我還像新娘伪嫁。我一直安慰自己,他們只是感情好偶垮,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布张咳。 她就那樣靜靜地躺著,像睡著了一般似舵。 火紅的嫁衣襯著肌膚如雪脚猾。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,784評(píng)論 1 290
  • 那天啄枕,我揣著相機(jī)與錄音婚陪,去河邊找鬼。 笑死频祝,一個(gè)胖子當(dāng)著我的面吹牛泌参,可吹牛的內(nèi)容都是我干的脆淹。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼沽一,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼盖溺!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起铣缠,我...
    開(kāi)封第一講書(shū)人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤烘嘱,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蝗蛙,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蝇庭,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年捡硅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了哮内。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡壮韭,死狀恐怖北发,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情喷屋,我是刑警寧澤琳拨,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站屯曹,受9級(jí)特大地震影響狱庇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜是牢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一僵井、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧驳棱,春花似錦、人聲如沸农曲。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)乳规。三九已至形葬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間暮的,已是汗流浹背笙以。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留冻辩,地道東北人猖腕。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓拆祈,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親倘感。 傳聞我的和親對(duì)象是個(gè)殘疾皇子放坏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

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