Head First設(shè)計(jì)模式——裝飾者模式

本文是閱讀 Head First 設(shè)計(jì)模式——裝飾者模式的總結(jié)。
這本書的教學(xué)模式很不錯(cuò),個(gè)人很喜歡,由實(shí)際的案例由淺入深,循序漸進(jìn)的讓你明白良好的設(shè)計(jì)是多么的優(yōu)雅迷人(回頭看看自己的代碼,WTF?。?br> 但是讀第二遍的時(shí)候,居然想不起來這章節(jié)說了什么,到底怎么解決這個(gè)問題的。也就是說,看的時(shí)候爽快,看完了并沒有應(yīng)用到具體的Coding中。

這一章節(jié)的案例是:設(shè)計(jì)星巴茲咖啡系統(tǒng)

相關(guān)的背景:
星巴茲咖啡現(xiàn)在有四種咖啡:黑咖啡(HouseBlend)、深度烘焙咖啡(DarkRoast)、脫咖啡因咖啡(Decaf)、濃咖啡(Espresso)。
用戶在購買咖啡時(shí),可以要求在其中加入各種調(diào)料,例如:蒸奶(Steamed Milk)、豆?jié){(Soy)、摩卡(Mocha)或覆蓋奶泡(Whip)。會(huì)根據(jù)所加入的調(diào)料收取不同的費(fèi)用,所以訂單系統(tǒng)必須考慮到這些調(diào)料的價(jià)格。

原先的類設(shè)計(jì)

類圖

如果按照這種模式,在某種咖啡中加入調(diào)料,那么就是一個(gè)新的子類,繼承 Beverage ,實(shí)現(xiàn)自己的 cost() 方法,算出咖啡以及調(diào)料的價(jià)格。以此類推,這是一個(gè)類爆炸的系統(tǒng),有多少種花樣就要為此設(shè)計(jì)多少類。

很明顯,星巴茲為自己制作了一個(gè)維護(hù)噩夢。如果牛奶漲價(jià)了,怎么辦?新增一種焦糖風(fēng)味調(diào)料時(shí),怎么辦?

很明顯,這種設(shè)計(jì)是有致命的缺陷的。

優(yōu)化方案

利用實(shí)例變量和繼承,可以追蹤調(diào)料,沒必要去設(shè)計(jì)這么多類。

類圖

這樣設(shè)計(jì)有哪些缺陷呢?

  • 調(diào)料價(jià)格變化時(shí),需要更改現(xiàn)有代碼
  • 出現(xiàn)新的調(diào)料,需要添加新的方法,并改動(dòng)超類中的cost()方法
  • 以后開發(fā)新的飲料,對于這些飲料,某些調(diào)料并不適合,但是這個(gè)設(shè)計(jì)中,子類仍需繼承那些不需要的方法
  • 萬一顧客想要雙倍摩卡怎么辦?

當(dāng)然這種設(shè)計(jì),違反了基本的開閉原則,類應(yīng)該對擴(kuò)展開放,對修改關(guān)閉。

裝飾者模式

從上面的方案來看,我們利用繼承無法完全解決問題,現(xiàn)在遇到的問題有:類數(shù)量爆炸、設(shè)計(jì)死板,以及基類加入的新功能并不適用所有的子類。

所以,在這里要采用不一樣的做法:以飲料為主體,然后再運(yùn)行時(shí)以調(diào)料來“裝飾”(decorate)飲料。比如,如果顧客想要摩卡和奶泡深焙咖啡,那么,要做的是:

  1. 拿一個(gè)深焙咖啡(DarkRoast)對象
  2. 以摩卡(Mocha)對象裝飾它
  3. 以奶泡(Whip)對象裝飾它
  4. 調(diào)用cost()方法,并依賴委托將調(diào)料的價(jià)錢加上去
訂單構(gòu)成1

訂單構(gòu)成2

上面是pdf文本的截圖,這個(gè)過程如果不畫出來,就漏掉了很重要的循序漸進(jìn)的過程。

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

裝飾者模式結(jié)構(gòu)

上圖是裝飾者模式的結(jié)構(gòu)類圖。

廢話不多說,看看我們的訂單系統(tǒng)該如何寫。

原始的類圖中,超類Beverage基本不用改動(dòng)。

public abstract class Beverage {
    String description = "";

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}

下面實(shí)現(xiàn)調(diào)料類的抽象類,也就是裝飾者類:

// 為了讓CondimentDecorator 能夠取代 Beverage,所有才繼承Beverage
public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}

下面是飲料實(shí)體類,按照上面的包裝圖例,我們就實(shí)現(xiàn)DarkRoast就好了。

public class DarkRoast extends Beverage {

    public DarkRoast() {
        description = "DarkRoast";
    }

    @Override
    public double cost() {
        return 1.99;
    }
}

下面是Mocha 和 Whip 的調(diào)料代碼

public class Mocha extends CondimentDecorator {
    Beverage beverage;

    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    public String getDescription() {
        return beverage.getDescription() + ", Mocha";
    }

    public double cost() {
        return 0.20 + beverage.cost();
    }
}
public class Whip extends CondimentDecorator {
    Beverage beverage;

    public Whip(Beverage beverage) {
        this.beverage = beverage;
    }

    public String getDescription() {
        return beverage.getDescription() + ", Whip";
    }

    public double cost() {
        return 0.10 + beverage.cost();
    }
}

好了,依照裝飾者模式,我們完成了基本的代碼實(shí)現(xiàn)。下面是測試代碼:

public class StarbuzzCoffee {
    public static void main(String[] args) {
        // 一杯DarkRoast,不需要調(diào)料
        Beverage beverage = new DarkRoast();
        System.out.println(beverage.getDescription() + " $" + beverage.cost());

        // 一杯DarkRoast,加雙份Mocha和奶泡
        Beverage beverage2 = new DarkRoast();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
    }
}

運(yùn)行結(jié)果如下:

DarkRoast $1.99
DarkRoast, Mocha, Mocha, Whip $2.49

Process finished with exit code 0

真實(shí)世界的裝飾者:Java I/O

文章本來是打算簡單的總結(jié)下該章節(jié)的內(nèi)容,方便以后資料的查找,回憶下知道大概講的是什么東西,寫下來發(fā)現(xiàn),如果不能夠?qū)⑦@個(gè)循序漸進(jìn)的過程寫下來,那么就失去這本書基本的宗旨。
上面的內(nèi)容基本都是章節(jié)的文本,主要是擔(dān)心自己理解誤導(dǎo)了讀者。

如果對文章的內(nèi)容感興趣,不妨去讀一下《Head First設(shè)計(jì)模式》。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 定義 動(dòng)態(tài)地將責(zé)任附加到對象上。想要擴(kuò)展功能,裝飾者提供有別于繼承的另一種選擇。 例子 現(xiàn)在有一家咖啡店,需要設(shè)計(jì)...
    angry_zxy閱讀 417評論 0 1
  • ps:本文主要來源 給愛用繼承的人一個(gè)全新的設(shè)計(jì)眼界.(可以在不修改底層代碼的情況下給你的或者別人的對象賦予新的職...
    jack_520閱讀 766評論 0 0
  • 本章可以稱為“給愛用繼承的人一個(gè)全新的設(shè)計(jì)眼界” ,我們即將再度探討典型的繼承濫用問題。你將在本章學(xué)到如何使用對象...
    黑夜0411閱讀 468評論 0 0
  • 裝飾者模式可以做到在不修改任何底層代碼的情況下,給對象增加的新的方法。首先,我們通過對一個(gè)現(xiàn)實(shí)問題的模擬分析,了解...
    六尺帳篷閱讀 1,016評論 0 9
  • 家財(cái)萬貫, 確換不得完美的家庭, 到頭來落得妻離子散, 那也只是空歡喜。 你得到你想要的一些, 固然會(huì)失去你忽略的...
    晨沅曦閱讀 336評論 0 9

友情鏈接更多精彩內(nèi)容