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

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

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

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

原先的類設(shè)計

類圖

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

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

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

優(yōu)化方案

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

類圖

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

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

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

裝飾者模式

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

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

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

訂單構(gòu)成2

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

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

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

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

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

原始的類圖中,超類Beverage基本不用改動。

public abstract class Beverage {
    String description = "";

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}

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

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

下面是飲料實體類,按照上面的包裝圖例,我們就實現(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();
    }
}

好了,依照裝飾者模式,我們完成了基本的代碼實現(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());
    }
}

運行結(jié)果如下:

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

Process finished with exit code 0

真實世界的裝飾者:Java I/O

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

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

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

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

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

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