裝飾器模式(Decorator Pattern)允許向一個(gè)現(xiàn)有的對(duì)象添加新的功能,同時(shí)又不改變其結(jié)構(gòu)。這種類型的設(shè)計(jì)模式屬于結(jié)構(gòu)型模式,它是作為現(xiàn)有的類的一個(gè)包裝。
情景
在廈門的小伙伴們一定對(duì)沙茶面很熟悉,作為廈門的一個(gè)特色小吃,大街小巷到處能看到掛有“沙茶面”招牌的面館,味道鮮美,可以加各種配料,是外地游客必吃的一種小吃。
假設(shè)面館現(xiàn)在要做一個(gè)沙茶面訂單系統(tǒng),能計(jì)算出每一碗面的價(jià)格。而面館有兩種面,一種沙茶面,一種清湯面,兩種面有不同的定價(jià),一開始我們可能會(huì)這樣設(shè)計(jì)的,定義兩種面:

每一碗面顧客可以加配料,有瘦肉、豬肝、魷魚等,每一種配料都有不同的價(jià)格,清湯面和沙茶面加了不同的料更是有多種價(jià)格,接下去怎么處理呢?可能你會(huì)想到用成員變量加繼承方式,如下:

這樣每一碗面都可以設(shè)置自己的配料,在計(jì)算價(jià)格調(diào)用cost()時(shí)就可以根據(jù)Nooddle對(duì)象是否含有對(duì)應(yīng)的配料,有的話再加上配料的價(jià)錢就能計(jì)算出這碗面的價(jià)格;
缺點(diǎn)
這樣處理有點(diǎn)問(wèn)題,假如現(xiàn)在面館又增加了一種配料,比如老板今天新進(jìn)了花蛤、肉丸,這時(shí)候Nooddle基類還得加上這兩種配料的成員,每加一種配料,基類就要改動(dòng)一次,這樣明顯違背了設(shè)計(jì)模式:對(duì)擴(kuò)展開發(fā),對(duì)修改關(guān)閉的重要原則。
方案
這時(shí)候我們就可以使用裝飾器模式了,它能使我們加每一種調(diào)料時(shí)可以不改變?cè)械拿娴膶傩?,極大地提高了靈活性和可擴(kuò)展性,下面介紹一下通過(guò)裝飾模式處理的步驟:
1. 創(chuàng)建Nooddle基類
抽象面條類有mDescription標(biāo)注是哪一種面條,mPrice為定價(jià),每一種面的價(jià)格和名稱都在子類重寫
public abstract class Nooddle {
public String mDescription = "Unknow Nooddle";
public float mPrice = 0.0f;
public String getDescription(){
return mDescription;
}
public float cost(){
return mPrice;
}
}
2. 實(shí)現(xiàn)兩種面:沙茶面和清湯面
假設(shè)沙茶面每碗不含任何配料定價(jià)15塊,清湯面每碗不含任何配料定價(jià)10塊
public class SatayNooddle extends Nooddle {
@Override
public String getDescription() {
return "SatayNooddle";
}
@Override
public float cost() {
return 15.0f;
}
}
public class LightSoupNooddle extends Nooddle {
@Override
public String getDescription() {
return "LightSoupNooddle";
}
@Override
public float cost() {
return 10.0f;
}
}
3. 創(chuàng)建抽象裝飾類
這個(gè)類持有一個(gè)Nooddle對(duì)象,而自己本身也是Nooddle類型,這樣能保持動(dòng)作一致性,都有cost()方法,而且cost可以在調(diào)用成員變量的cost()方法基礎(chǔ)上再加上自己額外的處理,即能計(jì)算被裝飾者的價(jià)格外還能加上自己的價(jià)格
abstract class DecorateNooddle extends Nooddle {
public Nooddle mNooddle;
public DecorateNooddle(Nooddle nooddle){
mNooddle = nooddle;
}
@Override
public String getDescription() {
return mNooddle.getDescription() + mDescription;
}
@Override
public float cost() {
return mNooddle.cost() + mPrice;
}
}
4. 實(shí)現(xiàn)兩種配料:瘦肉和豬肝,它們屬于裝飾者
public class LeanBurdening extends DecorateNooddle {
public LeanBurdening(Nooddle nooddle) {
super(nooddle);
}
@Override
public String getDescription() {
mDescription = " + Lean";
return super.getDescription();
}
@Override
public float cost() {
mPrice = 1.5f;
return super.cost();
}
}
public class LiverBurdening extends DecorateNooddle {
public LiverBurdening(Nooddle nooddle) {
super(nooddle);
}
@Override
public String getDescription() {
mDescription = " + Liver";
return super.getDescription();
}
@Override
public float cost() {
mPrice = 2.0f;
return super.cost();
}
}
這樣準(zhǔn)備工作就做好了,這里總結(jié)一下裝飾模式的工作流程,假設(shè)一個(gè)顧客點(diǎn)了一碗沙茶面,加了瘦肉和豬肝配料,這是就有如下處理:
1. 創(chuàng)建一個(gè)沙茶面對(duì)象;
2. 創(chuàng)建一個(gè)瘦肉配料對(duì)象,這個(gè)配料裝飾了沙茶面,即沙茶面對(duì)象是自己的成員變量,要計(jì)算價(jià)格時(shí)就可以把自己的價(jià)格機(jī)上沙茶面的價(jià)格;
3. 創(chuàng)建一個(gè)豬肝對(duì)象,同第2點(diǎn);
4. 由于裝飾對(duì)象兩種配料和被裝飾對(duì)象沙茶面都是Nooddle類型,所以計(jì)算價(jià)格時(shí)只需要計(jì)算最外圍的裝飾對(duì)象的價(jià)格就能得出這碗面的總價(jià)格了。
用如下代碼處理:
public class NooddleStoreDemo {
public static void main(String[] args){
Nooddle satayNooddle = new SatayNooddle();
satayNooddle = new LiverBurdening(satayNooddle);
satayNooddle = new LeanBurdening(satayNooddle);
System.out.println(satayNooddle.getDescription() + ", price: " + satayNooddle.cost());
Nooddle lightSoupNooddle = new LightSoupNooddle();
lightSoupNooddle = new LiverBurdening(lightSoupNooddle);
lightSoupNooddle = new LeanBurdening(lightSoupNooddle);
System.out.println(lightSoupNooddle.getDescription() + ", price: " + lightSoupNooddle.cost());
}
}
看下輸出的結(jié)果:
SatayNooddle + Liver + Lean, price: 18.5
LightSoupNooddle + Liver + Lean, price: 13.5

總結(jié)
一般的,我們?yōu)榱藬U(kuò)展一個(gè)類經(jīng)常使用繼承方式實(shí)現(xiàn),由于繼承為類引入靜態(tài)特征,并且隨著擴(kuò)展功能的增多,子類會(huì)很膨脹。這時(shí)就可以動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé),這樣裝飾類和被裝飾類能獨(dú)立發(fā)展,不相互耦合,裝飾模式是繼承的一個(gè)替代模式,裝飾模式可以動(dòng)態(tài)擴(kuò)展一個(gè)實(shí)現(xiàn)類的功能。
示例代碼戳這里。