黃燜雞米飯最熱賣的外賣之一,國(guó)人都喜歡吃,吃過黃燜雞米飯的應(yīng)該都知道,除了黃燜雞米飯主體外,還可以添加各種配菜,如土豆、香菇、鵪鶉蛋、青菜等。如果需要你來設(shè)計(jì)一套黃燜雞米飯結(jié)賬系統(tǒng),你該如何設(shè)計(jì)呢?
前置條件:主體:黃燜雞米飯 價(jià)格:16,配菜:土豆 價(jià)格:2、香菇 價(jià)格:2、鵪鶉蛋 價(jià)格:2、青菜 價(jià)格:1.5
這還不簡(jiǎn)單?看我的,你隨手就來了下面這段代碼。
public class HuangMenJiMiFan { // 黃燜雞價(jià)格 private double huangMenJiPrice = 16D;
// 土豆價(jià)格 private double potatoPrice = 2D; // 鵪鶉蛋價(jià)格 private double eggPrice = 2D;
// 香菇價(jià)格 private double mushroomPrice = 2D; // 青菜價(jià)格 private double vegPrice =
1.5D; // 總價(jià)格 private double totalPrice = 0D; // 訂單描述 private StringBuilder desc
= new StringBuilder("黃燜雞米飯 "); // 是否加土豆 private boolean hasPotato = false; //
是否加鵪鶉蛋 private boolean hasEgg = false; // 是否加香菇 private boolean hasMushroom =
false; // 是否加蔬菜 private boolean hasVeg = false; public HuangMenJiMiFan(){
this.totalPrice = this.huangMenJiPrice; } public void setHasPotato(boolean
hasPotato) { this.hasPotato = hasPotato; } public void setHasEgg(boolean
hasEgg) { this.hasEgg = hasEgg; } public void setHasMushroom(boolean
hasMushroom) { this.hasMushroom = hasMushroom; } public void setHasVeg(boolean
hasVeg) { this.hasVeg = hasVeg; } public String getDesc(){ if (hasEgg){
this.desc.append("+ 一份鵪鶉蛋 "); } if (hasMushroom){ this.desc.append("+ 一份香菇 ");
} if (hasPotato){ this.desc.append("+ 一份土豆 "); } if (hasVeg){
this.desc.append("+ 一份蔬菜 "); } return desc.toString(); } public double cost(){
if (hasEgg){ this.totalPrice +=this.eggPrice; } if (hasMushroom){
this.totalPrice +=this.mushroomPrice; } if (hasPotato){ this.totalPrice
+=this.potatoPrice; } if (hasVeg){ this.totalPrice +=this.vegPrice; } return
totalPrice; } }
只要在點(diǎn)黃燜雞米飯的時(shí)候,把添加的配菜設(shè)置成true
就好,這段代碼確實(shí)解決了黃燜雞米飯結(jié)算問題。但是我需要加兩份土豆呢?我需要添加一種新配菜呢?或者我新增一個(gè)黃燜排骨呢?這時(shí)候?qū)崿F(xiàn)起來就需要去改動(dòng)原來的代碼,這違背了設(shè)計(jì)模式的
開放-關(guān)閉原則
開放-關(guān)閉原則:類應(yīng)該對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉
上面的設(shè)計(jì)違背了開放-關(guān)閉原則,為了避免這個(gè)問題,采用裝飾者模式似乎是一種可行的解決辦法。
裝飾者模式:動(dòng)態(tài)的給一個(gè)對(duì)象添加一些額外的職責(zé),就增加功能來說,裝飾模式比生成子類更為靈活。
裝飾者模式的通用類圖如下:
從類圖中,我們可以看出裝飾者模式有四種角色:
* Component:核心抽象類,裝飾者和被裝飾者都需要繼承這個(gè)抽象類
* ConcreteComponent:對(duì)裝飾的對(duì)象,該類必須繼承Component
* Decorator:裝飾者抽象類,抽象出具體裝飾者需要裝飾的接口
* ConcreteDecorator:具體的裝飾者,該類必須繼承Decorator類,并且里面有一個(gè)變量指向Component抽象類
裝飾者模式的核心概念我們都知道了,那就來實(shí)現(xiàn)一把,用裝飾者模式來設(shè)計(jì)黃燜雞米飯的結(jié)賬系統(tǒng)。
Component類的設(shè)計(jì),仔細(xì)想想,不管黃燜雞米飯還是配菜都會(huì)涉及到金額計(jì)算。所以我們把該方法抽象到Component類。來設(shè)計(jì)我們黃燜雞米飯結(jié)賬系統(tǒng)的
Component類,我們?nèi)∶凶鯢ood,Food類的具體設(shè)計(jì)如下:
/** * 核心抽象類 */ public abstract class Food { String desc = "食物描述"; public
String getDesc() { return this.desc; } // 價(jià)格計(jì)算 public abstract double cost(); }
ConcreteComponent類是我們具體的被裝飾對(duì)象,我們這里的裝飾對(duì)象是黃燜雞米飯,我們來設(shè)計(jì)我們黃燜雞米飯的被裝飾對(duì)象Rice類,Rice
類的具體實(shí)現(xiàn)如下:
/** * 被裝飾者-黃燜雞米飯 */ public class Rice extends Food{ public Rice(){ this.desc
="黃燜雞米飯"; } @Override public double cost() { // 黃燜雞米飯的價(jià)格 return 16D; } }
Decorator類是裝飾者的抽象類,我們需要定義一個(gè)getDesc()的抽象接口,因?yàn)樵贔ood類中,getDesc()
不是抽象的,在后面的具體裝飾者中,需要重寫getDesc()類,所以我們需要將抽象在裝飾者這一層。我們來設(shè)計(jì)黃燜雞米飯結(jié)賬系統(tǒng)的裝飾者抽象類
FoodDecorator,F(xiàn)oodDecorator類的具體設(shè)計(jì)如下:
public abstract class FoodDecorator extends Food { // 獲取描述 public abstract
String getDesc(); }
ConcreteDecorator
類是具體的裝飾者,我們有四個(gè)具體的裝飾者,分別是土豆、香菇、鵪鶉蛋、青菜,具體的裝飾者需要做的事情是計(jì)算出被裝飾者裝飾完裝飾品后的總價(jià)格和更新商品的描述。四個(gè)具體裝飾者的設(shè)計(jì)如下:
public class Egg extends FoodDecorator { String desc = "雞蛋"; //
存放Component對(duì)象,該對(duì)象可能是被裝飾后的 Food food; public Egg(Food food){ this.food = food; }
// 計(jì)算總價(jià) 當(dāng)前Component對(duì)象的價(jià)格加上當(dāng)前裝飾者的價(jià)格 @Override public double cost() { return
food.cost() + 2D; } @Override public String getDesc() { return food.getDesc()+"
+ "+this.desc; } } public class Mushroom extends FoodDecorator { String desc =
"香菇"; Food food; public Mushroom(Food food){ this.food = food; } // 計(jì)算總價(jià)
@Override public double cost() { return food.cost() + 2D; } @Override public
String getDesc() { return food.getDesc()+" + "+this.desc; } } public class
Potato extends FoodDecorator { String desc = "土豆"; Food food; public
Potato(Food food){ this.food = food; } // 計(jì)算總價(jià) @Override public double cost() {
return food.cost() + 2D; } @Override public String getDesc() { return
food.getDesc()+" + "+this.desc; } } public class Veg extends FoodDecorator {
String desc = "蔬菜"; Food food; public Veg(Food food){ this.food = food; }
// 計(jì)算總價(jià) @Override public double cost() { return food.cost() + 1.5D; } @Override
public String getDesc() { return food.getDesc()+" + "+this.desc; } }
裝飾者的所有角色都實(shí)現(xiàn)完了,我們來測(cè)試一下使用裝飾者模式之后的黃燜雞結(jié)賬系統(tǒng),編寫一個(gè)App測(cè)試類。
public class App { public static void main(String[] args) { // 點(diǎn)一份米飯 Rice rice
= new Rice(); // 加個(gè)雞蛋 Egg egg = new Egg(rice); // 在加土豆 Potato potato = new
Potato(egg); // 再加一份白菜 Veg veg = new Veg(potato);
System.out.println(veg.getDesc()); System.out.println(veg.cost()); } }
測(cè)試結(jié)果
我們的描述和金額都是正確的,可能你還是沒怎么明白裝飾者模式,一起來看看我們的黃燜雞米飯被裝飾后的示意圖:
我們的黃燜雞米飯共有三層裝飾,第一層是雞蛋,第二層是土豆,第三層是蔬菜。我們?cè)谧詈笳{(diào)用價(jià)格計(jì)算和商品描述都是調(diào)用了最外層的裝飾者的方法,有點(diǎn)像遞歸一樣,每一層的裝飾者都有被前一個(gè)裝飾者裝飾后的黃燜雞米飯對(duì)象。里面會(huì)產(chǎn)生想遞歸一樣的調(diào)用。希望看完這張圖之后,對(duì)你理解裝飾者模式有幫助。
使用裝飾者模式之后的黃燜雞米飯結(jié)賬系統(tǒng),在新增配菜或者產(chǎn)品時(shí),我們不需要修改原先的功能,只需要對(duì)類進(jìn)行擴(kuò)展就好了,這完全遵循了開放-關(guān)閉原則。
裝飾者模式的優(yōu)點(diǎn)
* 裝飾類和被裝飾類可以獨(dú)立發(fā)展,而不會(huì)互相耦合,換句話說,就是Component類無須知道Decorator類,Decorator
類也不用知道具體的被裝飾者。
* 裝飾者模式是繼承關(guān)系的一個(gè)替代方案,從上面的黃燜雞米飯的案例中,我們可以看出,不管裝飾多少層,返回的對(duì)象還是Component
* 裝飾者模式可以動(dòng)態(tài)的擴(kuò)展一個(gè)實(shí)現(xiàn)類的功能
裝飾者模式的優(yōu)點(diǎn)
* 多層裝飾模式比較復(fù)雜,你可以想象一下剝洋蔥,如果最里面的裝飾出了問題,你的工作量會(huì)有多大?
最后多說一句,JDK 中的 java.io 就是用裝飾者模式實(shí)現(xiàn)的,有興趣的可以去深入了解一下。
源代碼 <https://github.com/BinaryBall/GOF23/tree/master/decorator>
文章不足之處,望大家多多指點(diǎn),共同學(xué)習(xí),共同進(jìn)步
最后
打個(gè)小廣告,歡迎掃碼關(guān)注微信公眾號(hào):「平頭哥的技術(shù)博文」,一起進(jìn)步吧。
熱門工具 換一換