裝飾者模式可以做到在不修改任何底層代碼的情況下叙身,給對(duì)象增加的新的方法芜茵。
首先夷都,我們通過(guò)對(duì)一個(gè)現(xiàn)實(shí)問(wèn)題的模擬分析,了解什么是裝飾者模式以及裝飾者模式的作用尤莺。
問(wèn)題提出
咖啡店在街頭隨處可見(jiàn)旅敷。我們以咖啡店的飲品訂單系統(tǒng)為例。假設(shè)我們要設(shè)計(jì)一個(gè)飲品的訂單系統(tǒng)颤霎。
設(shè)計(jì)了一個(gè)這樣的類(lèi)圖:
Beverage是一個(gè) 抽象類(lèi)媳谁,所有咖啡店的飲品都必須繼承這個(gè)類(lèi),description是飲品的描述信息友酱,cost()是計(jì)算此種飲品的價(jià)格晴音。
我們會(huì)遇到這樣的問(wèn)題,在購(gòu)買(mǎi)飲品的時(shí)候缔杉,我們可以要求在其中加入不同的調(diào)料配品锤躁,比如,摩卡(Mocha)或详,加奶泡等进苍。除了原本飲料需要的價(jià)格的外,咖啡店會(huì)根據(jù)所加入調(diào)料的再收取不同的費(fèi)用鸭叙。
如果按照之前的設(shè)計(jì)方式觉啊,那么會(huì)出現(xiàn)如下的情況:
** 顯然這似乎已經(jīng)是類(lèi)爆炸了!**
而且我們永遠(yuǎn)無(wú)法預(yù)測(cè)沈贝,顧客會(huì)選取怎樣的調(diào)料的搭配杠人,每當(dāng)出現(xiàn)一個(gè)新的調(diào)料搭配時(shí),我們就需要增加一個(gè)新的類(lèi)宋下。
更加糟糕的是嗡善,當(dāng)原料配料的價(jià)格上漲后或者下降后,那么所有涉及到這種配料的類(lèi)都得重新改過(guò)学歧。這簡(jiǎn)直是個(gè)噩夢(mèng)罩引!很顯然這很不符合我們?cè)O(shè)計(jì)模式的原則。作為一個(gè)程序員枝笨,我們是決不能容忍這種情況發(fā)生的袁铐!
那么我們?cè)撊绾卧O(shè)計(jì)呢揭蜒?
這里就需要用到我們的裝飾者模式!
引出裝飾者模式
讓我們轉(zhuǎn)換思路剔桨,我們以飲品beverage為主體屉更,在運(yùn)行時(shí)以顧客選擇的調(diào)料來(lái)裝飾beverage。比如洒缀,如果顧客想要摩卡和奶泡的拿鐵咖啡瑰谜,我們要做的應(yīng)該是這樣的:
- 取一個(gè)拿鐵咖啡的對(duì)象
- 用摩卡對(duì)象裝飾它
- 用奶泡對(duì)象裝飾它
- 調(diào)用cost方法計(jì)算價(jià)錢(qián),并依賴(lài)委托將配料摩卡和奶泡加上去树绩。
會(huì)先計(jì)算whip的cost然后調(diào)用mocha的cost萨脑,然后調(diào)用拿鐵的cost,這樣就計(jì)算出了總價(jià)格饺饭。
這樣就是實(shí)現(xiàn)的裝飾者模式解決這個(gè)問(wèn)題的思路砚哗。
下面我們看一下裝飾者模式的定義,以及代碼實(shí)現(xiàn)的基本思路
定義裝飾者模式
裝飾者模式動(dòng)態(tài)的將責(zé)任附加到對(duì)象上砰奕。若要擴(kuò)展功能蛛芥,裝飾者提供了比繼承更有彈性的替代方案。
這個(gè)類(lèi)圖就是裝飾者模式的實(shí)現(xiàn)方式军援。更詳細(xì)的是如下這個(gè)版本的類(lèi)圖仅淑。
下面我們就根據(jù)這個(gè)類(lèi)圖來(lái)解決我們之前在實(shí)現(xiàn)咖啡店飲料系統(tǒng)上遇到的問(wèn)題。
分析設(shè)計(jì)類(lèi)圖:
- beverage相當(dāng)于抽象的component類(lèi)胸哥,具體的component和decorator都需要繼承實(shí)現(xiàn)這個(gè)抽象類(lèi)涯竟。
- 四個(gè)具體的飲料的類(lèi),相當(dāng)于concrete component空厌!每一個(gè)類(lèi)代表了一個(gè)飲料類(lèi)型庐船。
- condimentDecorator是抽象的decorator類(lèi),它是所有調(diào)料類(lèi)的抽象嘲更,它保存了beverage的一個(gè)引用筐钟。
- 調(diào)料裝飾者類(lèi)繼承自condimentDecorator,是各種具體調(diào)料的實(shí)現(xiàn)赋朦,他們都實(shí)現(xiàn)了cost方法篓冲。
上面有一個(gè)非常關(guān)鍵的地方,就是我們注意到裝飾者和被裝飾者必須是一樣的類(lèi)型宠哄,也就是擁有共同的超類(lèi)壹将。這樣做是因?yàn)槲覀円b飾者必須能取代被裝飾者。
這樣我們就可以利用對(duì)象的組合毛嫉,將調(diào)料和飲料的行為組合起來(lái)诽俯。這符合我們之前提到的設(shè)計(jì)原則多用組合,少用繼承
實(shí)現(xiàn)裝飾者模式
如果看到這里還是不太清楚承粤,也沒(méi)關(guān)系暴区,接下來(lái)我們將具體實(shí)現(xiàn)代碼闯团,對(duì)裝飾者模式有一個(gè)直觀(guān)根本的了解。
- 首先實(shí)現(xiàn)beverage和condiment兩個(gè)抽象類(lèi)
package abstractComponent;
public abstract class Beverage {
protected String description = "Unknow Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
package abstractDecrator;
import abstractComponent.Beverage;
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
- 然后我們實(shí)現(xiàn)具體的飲料類(lèi)
package concreteComponent;
import abstractComponent.Beverage;
public class Coco extends Beverage {
public Coco(){
description = "Coco";
}
public double cost(){
return 0.89;
}
}
package concreteComponent;
import abstractComponent.Beverage;
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
}
public double cost() {
return 1.99;
}
}
- 我們?cè)賹?shí)現(xiàn)具體的裝飾者類(lèi)颜启,也就是調(diào)料類(lèi)
package concreteDecorator;
import abstractComponent.Beverage;
import abstractDecrator.CondimentDecorator;
public class Mocha extends CondimentDecorator {
Beverage beverage;
public Mocha(Beverage beverage){
this.beverage = beverage;
}
@Override
public double cost() {
// TODO Auto-generated method stub
return .20 + beverage.cost();
}
@Override
public String getDescription() {
// TODO Auto-generated method stub
return beverage.getDescription() + ", Mocha";
}
}
package concreteDecorator;
import abstractComponent.Beverage;
import abstractDecrator.CondimentDecorator;
public class Soy extends CondimentDecorator {
Beverage beverage;
public Soy(Beverage beverage){
this.beverage = beverage;
}
@Override
public String getDescription() {
// TODO Auto-generated method stub
return beverage.getDescription() + ", Soy";
}
@Override
public double cost() {
// TODO Auto-generated method stub
return .15 + beverage.cost();
}
}
package concreteDecorator;
import abstractComponent.Beverage;
import abstractDecrator.CondimentDecorator;
public class Whip extends CondimentDecorator {
Beverage beverage;
public Whip(Beverage beverage){
this.beverage = beverage;
}
@Override
public String getDescription() {
// TODO Auto-generated method stub
return beverage.getDescription() + " , whip";
}
@Override
public double cost() {
// TODO Auto-generated method stub
return .10 + beverage.cost();
}
}
- 最后編寫(xiě)一個(gè)測(cè)試類(lèi),來(lái)測(cè)試我們裝飾者模式的效果如何
import concreteComponent.Coco;
import concreteComponent.Espresso;
import concreteDecorator.Mocha;
import concreteDecorator.Soy;
import concreteDecorator.Whip;
import abstractComponent.Beverage;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
Beverage beverage = new Espresso();
System.out.println( beverage.getDescription() + "$" + beverage.cost());
Beverage beverage2 = new Coco();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println( beverage2.getDescription() + "$" + beverage2.cost());
Beverage beverage3 = new Espresso();
beverage3 = new Whip(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Soy(beverage3);
System.out.println( beverage3.getDescription() + "$" + beverage3.cost());
}
}
總結(jié)與分析
通過(guò)裝飾者模式我們可以很好的解決咖啡店的問(wèn)題浪讳,用裝飾者去包裝組件缰盏,可以達(dá)到很好的可擴(kuò)展性。
- 裝飾者模式用到的技術(shù)主要有兩種就是組合和委托淹遵,這幫助我們動(dòng)態(tài)的在運(yùn)行時(shí)加上新的行為口猜。
- 裝飾者模式意味著一群裝飾者類(lèi),這些類(lèi)用來(lái)包裝裝飾者透揣。
- 裝飾者和被裝飾者類(lèi)實(shí)際上具有相同類(lèi)型的济炎。
- 裝飾者可以在被裝飾者的行為前面或后面加上自己的行為,甚至完全覆蓋辐真。
- 但裝飾者模式的使用會(huì)導(dǎo)致出現(xiàn)很多小對(duì)象须尚,就是裝飾者對(duì)象,過(guò)度使用也會(huì)使程序變得復(fù)雜侍咱。