序
這個(gè)模式用做飯來(lái)說(shuō)確實(shí)感覺(jué)不錯(cuò),因?yàn)闀?shū)上也是這么打比方的忆畅,就是用咖啡衡未,還都是那么繞口的名字,感覺(jué)實(shí)在受不了家凯。所以此處用面條來(lái)打比方缓醋,原理相同,但我覺(jué)得這個(gè)應(yīng)該大家會(huì)更熟悉一些绊诲。
1送粱、背景
有各式各樣的面條,拉面掂之,手搟面抗俄,寬條面,面片等等板惑,當(dāng)然,每種面條由于制作工藝不同偎快,所以?xún)r(jià)錢(qián)也不一樣冯乘。同時(shí),面條還要配著鹵汁晒夹,湯料裆馒,所以可以加豬骨高湯,雞架高湯丐怯,蔬菜高湯喷好,羊肉湯,或者炸好的甜面醬读跷,西紅柿雞蛋醬等等梗搅。同樣,加不同的醬料,價(jià)錢(qián)也不同无切。
那么首先想到的是荡短,先做一個(gè)面條基類(lèi),然后從這個(gè)類(lèi)中派生出各種面條類(lèi)哆键,比如雞架高湯手搟面掘托,西紅柿雞蛋寬條面〖冢可是闪盔,一旦你這樣做了,將會(huì)出現(xiàn)以下問(wèn)題:
1)將會(huì)出現(xiàn)無(wú)窮多的子類(lèi)辱士,n種類(lèi)型的面條×m中類(lèi)型的鹵汁=m×n種類(lèi)型的產(chǎn)品(為了和不同形狀的面條區(qū)分泪掀,凡是上桌的都叫做“產(chǎn)品”)
2)無(wú)法多種材料進(jìn)行拼湊。比如你想吃拉面识补,配炸醬+蔬菜高湯族淮,那這種設(shè)計(jì)方法做不到,除非再新建一個(gè)類(lèi)凭涂,這樣無(wú)疑又增大了工作量祝辣。
3)無(wú)法修改價(jià)錢(qián)。目前物價(jià)越來(lái)越高切油,可能半年后蝙斜,西紅柿從原來(lái)的2塊一斤,漲到了4塊一斤澎胡,那么你必須修改所有加了鹵汁為西紅柿炒雞蛋的產(chǎn)品類(lèi)——估計(jì)累暈孕荠。
所以經(jīng)過(guò)上面問(wèn)題的討論,還是采用裝飾模式比較好攻谁。那么裝飾模式長(zhǎng)啥樣稚伍?不要走開(kāi),精彩繼續(xù)戚宦。
2个曙、裝飾模式
顧名思義,裝飾模式就是在原有的基礎(chǔ)上受楼,對(duì)原有的事物錦上添花垦搬。每次加一個(gè)裝飾,一方面擴(kuò)展了原有對(duì)象的特點(diǎn)艳汽,同時(shí)猴贰,原有對(duì)象的基本特質(zhì)保持不變。即咖啡再怎么變河狐,也還是咖啡米绕,無(wú)論你加了糖瑟捣、奶、豆?jié){义郑。面條也是蝶柿,你加什么鹵汁,湯料非驮,仍然還是面條交汤。只不過(guò)面條的味道變了,口感變了劫笙,價(jià)錢(qián)也變了芙扎。上代碼看下:
public class Test1 {
public static void main(String args[])
{
/*寬條面西紅柿雞蛋面 2+2=4
* 拉面羊肉湯面 3+3=6
* 寬面條西紅柿雞蛋,羊肉湯面 2+2+3=7
* */
WideNoodles wideNoodles1=new WideNoodles();//2
TomatoEgg tomatoEgg1=new TomatoEgg(wideNoodles1);//2
WideNoodles wideNoodles2=new WideNoodles();//2
TomatoEgg tomatoEgg2=new TomatoEgg(wideNoodles2);//2
GoatSoup goatSoup2=new GoatSoup(tomatoEgg2);//3
StretchNoodles stretchNoodles1=new StretchNoodles();//3
GoatSoup goatSoup1=new GoatSoup(stretchNoodles1);//3
System.out.println(tomatoEgg1.getDes()+tomatoEgg1.price());
System.out.println(goatSoup1.getDes()+goatSoup1.price());
System.out.println(goatSoup2.getDes()+goatSoup2.price());
}
}
abstract class Noodles //面條抽象類(lèi)
{
protected String description="noodles";
public abstract double price();
public String getDes(){ return description; }
}
//各種面條
class WideNoodles extends Noodles//寬條面
{
public WideNoodles() {this.description="WideNoodles";}
public double price() {return 2.0;}
}
class StretchNoodles extends Noodles//拉面
{
public StretchNoodles() {this.description="StretchNoodles";}
public double price(){ return 3.0;}
}
//鹵汁類(lèi)
class Bittern extends Noodles////基本鹵汁
{
protected Noodles noodles;//面條
public Bittern(Noodles n){this.description="Bittern"; this.noodles=n;}//修改成鹵汁類(lèi)型
public double price(){return this.noodles.price()+1.0;}//面條+1塊錢(qián)鹵汁
public String getDes() {return this.description+","+this.noodles.getDes();}//面條+鹵汁
}
//具體鹵汁填大,由于上面鹵汁基類(lèi)的方法都已經(jīng)重新戒洼,所以只需要修改price即可
class TomatoEgg extends Bittern//西紅柿炒雞蛋鹵汁
{
public TomatoEgg(Noodles n) {super(n); this.description="TomatoEgg";}
public double price(){return this.noodles.price()+2.0;}
}
class GoatSoup extends Bittern//羊肉湯鹵汁
{
public GoatSoup(Noodles n) {super(n);this.description="GoatSoup";}
public double price() {return this.noodles.price()+3.0;}
}
class ChickSoup extends Bittern//雞架鹵汁
{
public ChickSoup(Noodles n) {super(n);this.description="ChickSoup";}
public double price() {return this.noodles.price()+4.0;}
}
開(kāi)始解析上面的代碼。剛看到工作的同學(xué)準(zhǔn)備招聘高級(jí)java程序員允华。讀研的傷不起啊圈浇。。靴寂。
1)從程序中可以看到磷蜀,所有的類(lèi)都有一個(gè)共同的抽象基類(lèi),即Noodles百炬,因?yàn)楫a(chǎn)品無(wú)論怎么變化褐隆,本質(zhì)是面條,也應(yīng)正了繼承是Is—a的規(guī)律剖踊。面條都有描述和價(jià)錢(qián)庶弃。
2)每一種面條都作為獨(dú)立的個(gè)體,而鹵汁不一樣德澈,鹵汁需要依附于面條歇攻,也就是有了面條,鹵汁才能修飾作為裝飾者梆造,去修飾面條缴守。所以鹵汁中包含了面條主體。而鹵汁+面條=面條澳窑,所以面條的本質(zhì)沒(méi)有變斧散。
(注意修飾和被修飾的關(guān)系供常,這樣才能確定組合的情況)
3摊聋、Java中的具體實(shí)現(xiàn)
在Java.io.*中,就是這樣的道理栈暇。所有的IO類(lèi)都有一個(gè)共同的基類(lèi)麻裁,表明這些類(lèi)都是I/O操作,本質(zhì)沒(méi)有變。但是煎源,對(duì)于真正做讀寫(xiě)操作的(就好像本文中的面條)色迂,作為一個(gè)獨(dú)立實(shí)體,這些類(lèi)只能一次讀取一個(gè)字節(jié)手销,或者一行歇僧。而修飾這些進(jìn)行實(shí)際讀寫(xiě)操作的裝飾者,則可能只是封裝了一個(gè)緩存锋拖,或者將讀取的字符轉(zhuǎn)換成小寫(xiě)等等诈悍。
4、其他方面
其實(shí)說(shuō)的再遠(yuǎn)一點(diǎn)兽埃。Unix中的管道也是這種思想侥钳,每個(gè)程序都提供了讀取數(shù)據(jù)和寫(xiě)出數(shù)據(jù)的接口,然后通過(guò)管道柄错,可以一次又一次的傳遞數(shù)據(jù)舷夺,比如:ls /root | more | grep "Noodles"。當(dāng)然售貌,more只是為了說(shuō)明给猾,實(shí)際沒(méi)啥用。