設計模式——裝飾者模式

本文是閱讀 Head First 設計模式——裝飾者模式的總結。 這本書的教學模式很不錯,我的很喜歡,由實際的案例由淺入深,按部就班的讓你明白良好的設計是多麼的優雅迷人(回頭看看本身的代碼,WTF!)。 可是讀第二遍的時候,竟然想不起來這章節說了什麼,到底怎麼解決這個問題的。也就是說,看的時候爽快,看完了並無應用到具體的Coding中。java

這一章節的案例是:設計星巴茲咖啡系統設計模式

相關的背景:
星巴茲咖啡如今有四種咖啡:黑咖啡(HouseBlend)、深度烘焙咖啡(DarkRoast)、脫咖啡因咖啡(Decaf)、濃咖啡(Espresso)。
用戶在購買咖啡時,能夠要求在其中加入各類調料,例如:蒸奶(Steamed Milk)、豆漿(Soy)、摩卡(Mocha)或覆蓋奶泡(Whip)。會根據所加入的調料收取不一樣的費用,因此訂單系統必須考慮到這些調料的價格。ide

原先的類設計

類圖

若是按照這種模式,在某種咖啡中加入調料,那麼就是一個新的子類,繼承 Beverage ,實現本身的 cost() 方法,算出咖啡以及調料的價格。以此類推,這是一個類爆炸的系統,有多少種花樣就要爲此設計多少類。測試

很明顯,星巴茲爲本身製做了一個維護噩夢。若是牛奶漲價了,怎麼辦?新增一種焦糖風味調料時,怎麼辦?優化

很明顯,這種設計是有致命的缺陷的。this

優化方案

利用實例變量和繼承,能夠追蹤調料,不必去設計這麼多類。spa

類圖

這樣設計有哪些缺陷呢?設計

  • 調料價格變化時,須要更改現有代碼
  • 出現新的調料,須要添加新的方法,並改動超類中的cost()方法
  • 之後開發新的飲料,對於這些飲料,某些調料並不適合,可是這個設計中,子類仍需繼承那些不須要的方法
  • 萬一顧客想要雙倍摩卡怎麼辦?

固然這種設計,違反了基本的開閉原則,類應該對擴展開放,對修改關閉。3d

裝飾者模式

從上面的方案來看,咱們利用繼承沒法徹底解決問題,如今遇到的問題有:類數量爆炸、設計死板,以及基類加入的新功能並不適用全部的子類。code

因此,在這裏要採用不同的作法:以飲料爲主體,而後再運行時以調料來「裝飾」(decorate)飲料。好比,若是顧客想要摩卡和奶泡深焙咖啡,那麼,要作的是:

  1. 拿一個深焙咖啡(DarkRoast)對象
  2. 以摩卡(Mocha)對象裝飾它
  3. 以奶泡(Whip)對象裝飾它
  4. 調用cost()方法,並依賴委託將調料的價錢加上去

訂單構成1
訂單構成2

上面是pdf文本的截圖,這個過程若是不畫出來,就漏掉了很重要的按部就班的過程。

裝飾者模式:動態的將責任附加到對象上。如果要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。

裝飾者模式結構

上圖是裝飾者模式的結構類圖。

廢話很少說,看看咱們的訂單系統該如何寫。

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

public abstract class Beverage {
    String description = "";

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}
複製代碼

下面實現調料類的抽象類,也就是裝飾者類:

// 爲了讓CondimentDecorator 可以取代 Beverage,全部才繼承Beverage
public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}
複製代碼

下面是飲料實體類,按照上面的包裝圖例,咱們就實現DarkRoast就行了。

public class DarkRoast extends Beverage {

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

    @Override
    public double cost() {
        return 1.99;
    }
}
複製代碼

下面是Mocha 和 Whip 的調料代碼

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();
    }
}
複製代碼

好了,依照裝飾者模式,咱們完成了基本的代碼實現。下面是測試代碼:

public class StarbuzzCoffee {
    public static void main(String[] args) {
        // 一杯DarkRoast,不須要調料
        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());
    }
}
複製代碼

運行結果以下:

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

Process finished with exit code 0
複製代碼

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

文章原本是打算簡單的總結下該章節的內容,方便之後資料的查找,回憶下知道大概講的是什麼東西,寫下來發現,若是不可以將這個按部就班的過程寫下來,那麼就失去這本書基本的宗旨。 上面的內容基本都是章節的文本,主要是擔憂本身理解誤導了讀者。

若是對文章的內容感興趣,不妨去讀一下《Head First設計模式》。

相關文章
相關標籤/搜索