本文是閱讀 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)飲料。比如,如果顧客想要摩卡和奶泡深焙咖啡,那么,要做的是:
- 拿一個(gè)深焙咖啡(DarkRoast)對象
- 以摩卡(Mocha)對象裝飾它
- 以奶泡(Whip)對象裝飾它
- 調(diào)用
cost()方法,并依賴委托將調(diào)料的價(jià)錢加上去


上面是pdf文本的截圖,這個(gè)過程如果不畫出來,就漏掉了很重要的循序漸進(jìn)的過程。
裝飾者模式:動(dòng)態(tài)的將責(zé)任附加到對象上。若是要擴(kuò)展功能,裝飾者提供了比繼承更有彈性的替代方案。

上圖是裝飾者模式的結(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ì)模式》。