思考題
有如下類設計:
如果牛奶的價錢上揚,怎麼辦?新增一種焦糖調料風味時,怎麼辦?
造成這種維護上的困難,違反了我們之前提過的哪種設計原則?
P82
- 取出并封裝變化的部分,讓其他部分不收影響
- 多用組合,少用繼承
請為下面類的 cost() 方法書寫代碼。
P83
抽象類:Beverage
public class Beverage {
public double cost() {
double totalCost = 0.0;
if (hasMilk()) {
totalCost += milkCost;
}
if (hasSoy()) {
totalCost += soyCost;
}
if (hasMocha()) {
totalCost += mochaCost;
}
if (hasWhip()) {
totalCost += whipCost;
}
return totalCost;
}
}
具體類:DarkRoast
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "Most Excellent Dark Roast";
}
public double cost() {
return baseCost + super.cost();
}
}
當哪些需求或因素改變時會影響這個設計?
P84
- 調料價錢的改變會使我們改變現有代碼
- 一旦出現新的調料,我們就需要加上新的方法,并改變超類中的 cost() 方法
- 以後可能會開發出新飲料。對這些飲料而言(例如:冰茶),某些調料可能并不适合,但是在這個設計方式中,Tea (茶)子類仍然将繼承那些不适合的方法,例如:hasWhip() (加奶泡)
- 萬一顧客想要雙倍摩卡或咖啡,怎麼辦?
- 調料價錢随着具體飲料而改變
- 飲料基礎價錢随着大中小被的不同而改變
設計原則
- 開閉原則:類應該對擴充開放,對修改關閉
P86
- 政策模式、觀察者模式和裝飾器模式均遵循開閉原則
P105
- 政策模式、觀察者模式和裝飾器模式均遵循開閉原則
裝飾器模式
動态地将責任附加到對象上,而不改變其原有代碼。若擴充功能,裝飾器提供了比繼承更優彈性的替代方案。
P91
特點
- 裝飾類和被裝飾類有相同的超類型
P90
- 裝飾類可以在所委托的被裝飾類的行為之前(或之後),加上自己的行為,以達到特定的目的
P90
- 可以透明地插入裝飾器,使用時甚至不需要知道是在和裝飾器互動
P104
- 适合用來建立有彈性的設計,維持開閉原則
P104
缺點
- 存在大量小類,使用時将會增加代碼複雜度
P101
P104
- 使用時依賴某種特殊類型,然後忽然導入裝飾器,卻又沒有周詳地考慮一切
P104
我們在星巴茲的朋友決定開始在菜單上加上咖啡的容量大小,供顧客可以選擇小貝(tall)、中杯(grande)、大杯(venti)。星巴茲認為這是任何咖啡都必須具備的,是以在 Beverage 類中加上了 getSize() 與 setSize() 。他們也希望調料根據咖啡容量收費,例如:小中大杯的咖啡加上豆漿,分别加收 0.10、0.15、0.20 美金。
如何改變裝飾者類應對這一的需求?
P99
public class Soy extends CondimentDecorator {
Beverage beverage;
public Soy(Beverage beverage) {
this.beverage = beverage;
}
public int getSize() {
return beverage.getSize();
}
public void setSize(int size) {
beverage.setSize(size);
}
public String getDescription() {
return beverage.getDescription() + ", Soy";
}
public double cost() {
double soyCost = 0.0;
switch (getSize()) {
case TALL:
soyCost = 0.10;
break;
case GRANDE:
soyCost = 0.15;
break;
case VENTI:
soyCost = 0.20;
break;
default:
soyCost = 0.0;
}
return beverage.cost() + soyCost;
}
}
所思所想
- 裝飾器模式使用了繼承(或實作接口)的方式,是以超類型增加方法時,所有子類都需要改變,設計時要充分考慮
- 總感覺裝飾器模式很熟悉,看了 java-design-patterns/decorator 後,發現裝飾器模式的思想平常都是運用在 AOP 中實作的 (最後學了代理模式發現原來是動态代理模式)
本文首發于公衆号:滿賦諸機( 點選檢視原文 ) 開源在 GitHub : reading-notes/head-first-design-patterns