學好設計模式防被祭天:裝飾者模式
為了防止被“殺”了祭天,學點設計模式,並總結下還是有必要的。
一:理解
- 顧名思義,裝飾者模式可以在不修改基礎類的前提下,增加/修改物件的屬性。
- 提到裝飾者模式,立即想到《Head First 設計模式》中,奶茶的例子。
- 裝飾者模式的新建過程大致為A a = new C(new B(new A()))。
- 無論裝飾多少層,都可以賦值給a物件,表示裝飾者和被裝飾者屬於同一個繼承體系。
二:例子
你是個低調的富二代,不抽菸,不喝酒,沒事就喜歡在街頭走一走,喔哦哦,你會把手揣進褲兜。
有一天,你發現街邊有一家叫一丟丟的奶茶店超級火爆。
於是,你決定投資一個億,開一家飲料店。
你叫來程式設計師小菜幫忙設計飲料店系統。
小菜自稱一把梭,上來就是幹,立馬新建了一個飲料基類,如下:
public abstract class Drink {
public abstract String getDesc();
public abstract double getCost();
}
飲料基類擁有兩個抽象方法,一個返回飲料名稱,一個返回飲料價格。
看到小菜這麼積極主動,並且用上了抽象類。你也裝作很懂的樣子,說了句:一上手就知道是老江湖了。
於是你很放心地讓小菜設計接下來的內容。
你的需求是:
- 飲料店會售賣4種飲料,包括奶茶(MilkTea),咖啡(Coffee),果汁(Juice),蘇打水(Soda)。
- 可以在飲料上加奶泡(Whip),糖霜(Sugar),珍珠(Pearl)。
小菜覺得飲料的種類和加料不是很多,三下五除二就把整個程式給搞定了。
// 奶茶類
public class MilkTea extends Drink {
@Override
public String getDesc() {
return "奶茶";
}
@Override
public double getCost() {
return 10;
}
}
// 奶茶加奶泡類
public class MilkTeaWithWhip extends Drink {
@Override
public String getDesc() {
return "奶茶加奶泡";
}
@Override
public double getCost() {
return 10 + 2;
}
}
// 奶茶加奶泡加糖霜類
public class MilkTeaWithWhipAndSugar extends Drink {
@Override
public String getDesc() {
return "奶茶加奶泡加糖霜";
}
@Override
public double getCost() {
return 10 + 2 + 1;
}
}
// 奶茶加奶泡加糖霜加珍珠類
public class MilkTeaWithWhipAndSugarAndPearl extends Drink {
@Override
public String getDesc() {
return "奶茶加奶泡加糖霜加珍珠";
}
@Override
public double getCost() {
return 10 + 2 + 1 + 1;
}
}
// 奶茶加糖霜類
// 奶茶加糖霜加珍珠類
// ……
// 咖啡加XXX類
// ……
搞定了奶茶MilkTea類,奶茶可以不加料,加一種料,兩種料,三種料,一共是1 + 3 + 3 + 1 = 8個類。
小菜接著搞定咖啡,果汁,蘇打水類,一種是4 * 8 = 32個類,加上基類Drink,一共是33個類。
於是,小菜在一片需求不飽和的議論聲中,在六點準時下班了,並期待第二天得到富二代的表揚。
飲料店順利開張了,和意料中的一樣,生意超級火爆。
多了一個奶茶店店主title的你,很是高興,一有空就去隔壁老王的奶茶店調研。
你發現王叔叔奶茶店可以讓顧客選擇不同的甜度,從不加糖加十分甜,一共11個等級。
你覺得很有創意,於是你迫不及待地叫小菜加上這個功能。
小菜沒多想,立馬答應了下來,並表示在做完手頭的需求之後,立馬就做。
夜深人靜,小菜拿出鍵盤,準備一段敲。
突然發現,原來有32個類,每個類都需要擴充套件成從0級甜度到10級甜度。
一算下來,類會被擴充套件到32 * 11=352。
不過富二代第二天就要看到結果,小菜還是決定先做完功能性需求再說。
於是啪啪啪一頓敲,終於寫完了這352個類。
然而第二天,小菜接到通知,糖霜的價格上漲,加糖霜從一塊變成了兩塊。
不會全文搜尋的小菜修改了所有加了糖霜的飲料類之後,累翻在電腦桌前。
儘管如此,小菜還是忘改了一個奶茶加糖霜加A加B加C加D加E加F……類。
在收益計算的時,你發現少了一些錢,瞬間感覺自己損失了兩個億。
於是準備殺了這位程式設計師祭天。
小菜急忙解釋道自己立馬開始重構,一個類就能搞定。
@Data
public class DrinkRe {
private boolean hasWhip;
private boolean hasSugar;
private boolean hasPearl;
private int sugarIndex;
private int cost;
private String name;
public DrinkRe(boolean hasWhip, boolean hasSugar, boolean hasPearl, int sugarIndex, int cost, String name) {
this.hasWhip = hasWhip;
this.hasSugar = hasSugar;
this.hasPearl = hasPearl;
this.sugarIndex = sugarIndex;
this.cost = cost;
this.name = name;
}
public String getDesc() {
StringBuilder drinkDesc = new StringBuilder();
drinkDesc.append(name);
if (hasWhip) {
drinkDesc.append("加奶泡");
}
if (hasSugar) {
drinkDesc.append("加糖霜");
}
if (hasPearl) {
drinkDesc.append("加珍珠");
}
if (sugarIndex >= 0) {
drinkDesc.append(sugarIndex + "分甜");
}
return drinkDesc.toString();
}
public double getSumCost() {
int sumCost = cost;
if (hasWhip) {
sumCost += 2;
}
if (hasSugar) {
sumCost += 2;
}
if (hasPearl) {
sumCost += 1;
}
return sumCost;
}
public static void main(String[] args) {
DrinkRe coffee = new DrinkRe(true, true, true, 7, 10, "咖啡");
System.out.println(coffee.getDesc());
System.out.println(coffee.getSumCost());
}
}
這個飲料類在構造時傳入基礎飲料的名稱和價格,並在輸出描述和價格之前都先判斷是否有加料。
又過了幾天,飲料店愈發火爆,你開始提出新的需求:
- 飲料增加20種。
- 加料增加30種。
- 加糖霜描述之後加上(木糖醇)字樣,以示很健康。
小菜想了下:
- 一個類的情況在構造飲料時,每次都要手動輸入飲料的名字和價格。
- hasXXX,在DrinkRe中增加30個屬性,一個好長的建構函式。
- 需要寫n多個if判斷語句。
- 需求三倒是容易搞定,但又擔心之後又要改回來。
於是,一臉懵X的小菜請教了資深老司機,老司機先開了一會車,不緊不慢地告訴他,裝飾者模式可以解決這個問題。
裝飾者模式包含:
- 基類Drink,申明瞭飲料類有getDesc和getCost兩個方法。
- 被裝飾者,如基礎款奶茶MilkTea,實現了兩個抽象方法。
- 裝飾者,加了糖霜的飲料DrinkWithSugar,該類不關心把糖霜加在奶茶還是咖啡上,只負責把糖霜載入飲料上。
- DrinkWithSugar包含一個Drink屬性,在建構函式中,就設定傳入的drink物件。
- DrinkWithSugar繼承自Drink父類,即使加了糖霜之後,它也還是一杯飲料。
- DrinkWithSugar重寫getDesc和getCost方法,分別加上“加糖霜(木糖醇)”字樣,和加上糖霜的價格。
// 飲料基類
public abstract class Drink {
public abstract String getDesc();
public abstract double getCost();
}
// 被裝飾者
public class MilkTea extends Drink {
@Override
public String getDesc() {
return "奶茶";
}
@Override
public double getCost() {
return 10;
}
}
// 裝飾者類
public class DrinkWithSugar extends Drink {
private Drink drink;
public DrinkWithSugar(Drink drink) {
this.drink = drink;
}
@Override
public String getDesc() {
return drink.getDesc() + "加糖霜(木糖醇)";
}
@Override
public double getCost() {
return drink.getCost() + 2;
}
}
// 測試
public class Client {
public static void main(String[] args) {
Drink drinkWithSugar = new DrinkWithSugar(new MilkTea());
System.out.println(drinkWithSugar.getDesc());
System.out.println(drinkWithSugar.getCost());
}
}
輸入/輸出:
奶茶加糖霜(木糖醇)
12.0
用上裝飾者模式之後,整個系統不需要修改,只需要新增新的類。
在需要增加新的飲料時,只需新增一個新的飲料類,如Milk。
在需要增加新的加料時,也只需要加一個新的加料類,如DrinkWithSugarFree,用於做加糖免費活動。
在快速的新品,活動迭代下,隔壁王叔叔奶茶店倒閉了。
作為富二代的你看了很高興,準備好好獎勵下小菜。
奶茶店小妹也向程式設計師投來了崇拜的眼神,上來就是一頓酥麻酥麻的誇獎。
然而,第二天,程式設計師就看到奶茶店小妹出現在了富二代的跑車裡。
三:再理解
- 裝飾者模式是對繼承的再思考。
- 加料之後,還是一杯飲料,被裝飾者和裝飾者都繼承自同一個父類。
- 對修改關閉,對新增開放,符合設計原則,新增新功能只需增加少量程式碼。
- 避免了大量的if else程式碼,邏輯清晰。
相關文章
- [設計模式]裝飾者模式設計模式
- 設計模式——裝飾者模式設計模式
- 設計模式-裝飾者模式設計模式
- 設計模式之裝飾者模式設計模式
- 小白設計模式:裝飾者模式設計模式
- 設計模式學習筆記之裝飾者模式設計模式筆記
- Java設計模式之裝飾者模式Java設計模式
- 設計模式系列10–裝飾者模式設計模式
- PHP設計模式之裝飾者模式PHP設計模式
- 設計模式之裝飾者模式(二)設計模式
- 設計模式之裝飾者模式(一)設計模式
- Java設計模式(3)-裝飾者模式Java設計模式
- SpringBoot實踐設計模式———裝飾者模式Spring Boot設計模式
- 設計模式第三講-裝飾者模式設計模式
- Head First 設計模式(3)----裝飾者模式設計模式
- 設計模式——裝飾模式設計模式
- 設計模式-裝飾模式設計模式
- 設計模式學習-裝飾模式,橋接模式設計模式橋接
- 8.java設計模式之裝飾者模式Java設計模式
- 設計模式——從HttpServletRequestWrapper瞭解裝飾者模式設計模式HTTPServletAPP
- 設計模式(八)Context中的裝飾者模式設計模式Context
- 設計模式 | 裝飾者模式及典型應用設計模式
- Java學設計模式之裝飾器模式Java設計模式
- 設計模式(十一)----結構型模式之裝飾者模式設計模式
- [設計模式] 裝飾器模式設計模式
- 設計模式-裝飾器模式設計模式
- 設計模式----裝飾器模式設計模式
- 裝飾模式(裝飾設計模式)詳解——小馬同學@Tian設計模式
- 裝飾設計模式設計模式
- 修飾者模式(裝飾者模式,Decoration)模式
- 設計模式(九)——裝飾者模式(io原始碼分析)設計模式原始碼
- 每天一個設計模式之裝飾者模式設計模式
- 軟體設計模式學習(十三)裝飾模式設計模式
- 08.裝飾者模式設計思想模式
- 軟體設計模式————(裝飾模式)設計模式
- java設計模式--裝飾器模式Java設計模式
- GoLang設計模式21 - 裝飾模式Golang設計模式
- 設計模式(六):裝飾器模式設計模式
- 設計模式之-裝飾器模式設計模式