繼承可以在復用父類代碼的情況下擴展父類的功能,但同時繼承增加了對象之間的耦合度,所以要慎用繼承。那么有沒有既能擴展父類的功能,又能使對象間解耦的方法呢?答案是肯定的,這就是我們今天要學習的裝飾者模式。待會你會看到我會用裝飾者模式組裝一臺電腦。不過現(xiàn)在還是先把書上的例子學習一下。

 

學習書上的例子

Starbuzz咖啡店的系統(tǒng)需要更新一下,他們原來的系統(tǒng)是這樣的:

 

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓

可以看到,顧客購買飲料時有具體的子類提供并返回飲料的價格。購買咖啡時,可以在其中加入一些調(diào)料,比如蒸奶(Steamed Milk)、豆?jié){(Soy)、摩卡(Mocha,也就是巧克力風味)或覆蓋奶泡。Starbuzz會根據(jù)所加入的調(diào)料收取不同的費用。那么這怎么做呢?也許我們會想到這樣的幾種解決方法:

1.列出所有的飲料和調(diào)料的組合方式。好吧,我想沒有人會這么做,這樣組合情況太多,用書上的一種說法叫“類爆炸”。

2.在Beverage類中設(shè)置各種調(diào)料的boolean值以表示是否需要這種調(diào)料,如boolean milk, 然后用cost計算出加入各種調(diào)料后的價格,然后在子類的cost方法中調(diào)用父類的cost方法并加上飲料本身的價格。

分析第2中情況:聽起來還不錯,但一旦加入新的調(diào)料就得修改Beverage類。如果研究出了一種新型的飲料,里面的某些調(diào)料可能并不合適,這樣導致了飲料擁有加入不合適的調(diào)料的方法,這樣有什么后果,這樣可能會出現(xiàn)一些不好的后果,我們在策略模式一章中就受到教訓了(橡皮鴨會飛)。還有如果我想要雙倍摩卡了,怎么辦?

 

嘗試解決問題

現(xiàn)在問題已經(jīng)出現(xiàn)了,怎么解決呢?人們購買咖啡很自然的狀態(tài)可能是這樣的:先購買一杯咖啡,然后想要什么調(diào)料就購買什么調(diào)料,想要多少就購買多少。于是我們想這樣是否能解決問題:先創(chuàng)建一杯咖啡,然后創(chuàng)建調(diào)料并和咖啡動態(tài)的組合在一起。通過動態(tài)地組合對象,可以寫新的代碼添加新功能,而無須修改現(xiàn)有代碼。既然沒有改變現(xiàn)有代碼,那么引進bug或產(chǎn)生意外副作用的機會將大幅度減少。這就需要用到裝飾者模式。

 

軟件設(shè)計原則

開放-關(guān)閉原則:類應(yīng)該對擴展開放,對修改關(guān)閉。

 

定義裝飾者模式

動態(tài)地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。

 

讓Starbuzz的飲料符合裝飾者模式

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓

需要解釋一個這個圖:Beverage類是飲料的抽象類,所有的飲料都要繼承自這個類,它有一個獲得描述的方法(getDescription())和一個計算價格的抽象方法cost()。4個具體的咖啡類(如Espresso的)繼承了Beverage類并重寫了cost方法。CondimentDecorator類是一個抽象的裝飾類,也繼承了Beverage,Milk等是具體的裝飾類,在計算價格時在飲料的價格上再加上調(diào)料的價格,在獲得描述時在描述飲料的時候加上了調(diào)料的描述,所以說裝飾者增加了行為到被裝飾者的對象上。

前面說過要慎用繼承,裝飾者模式是通過動態(tài)的組合對象來添加新的功能,那么這里的CondimentDecorator類為什么繼承了Beverage類呢?其實,這里使用繼承并不是為了繼承行為,而是為了保持類型匹配。也就是說在需要被裝飾者類型的時候可以用裝飾者類型替換。這樣可能不太明白,我舉個CondimentDecorator沒有繼承Beverage的例子:假如顧客點了一杯濃縮咖啡Espresso,需要加入的調(diào)料為牛奶Milk和摩卡Mocha,我們需要先創(chuàng)建一杯Espresso從而得到espresso對象,然后將espresso對象作為參數(shù)傳入創(chuàng)建Milk對象,CondimentDecorator milk = new Milk(espresso); 這樣就在濃縮咖啡中加入了牛奶,可是還需要加入摩卡啊,這樣是不能同時加入牛奶和摩卡的,所以CondimentDecorator繼承Beverage是為了保持類型匹配。

 

開始工作

首先需要抽象的飲料和具體的飲料

Beverage:

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓 View Code

 

濃縮咖啡Espresso:

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓 View Code

具體的飲料只具有自己的描述和價格,其他具體的飲料見下面附錄A

接著添加抽象裝飾者和具體裝飾者,具體的調(diào)料裝飾者將自己的價格和描述附加到飲料的價格和描述上。

 

抽象調(diào)料裝飾者CondimentDecorator:

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓 View Code

 

具體調(diào)料裝飾者摩卡Mocha:

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓 View Code

其他具體調(diào)料見下面附錄B

其實可以看到,每個具體的調(diào)料類中都要Beverage對象的引用,既然這樣可以把Beverage對象引用放到CondimentDecorator類中,大家可以自己調(diào)整一下,這里我就不做調(diào)整了。我會在后面組裝電腦的例子中把被裝飾類的引用放到抽象裝飾類中。

 

測試一下DecoratorTest:

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓 View Code

 

裝飾者該做什么:

通過例子可以看到裝飾者該做的是:裝飾者該做的事就是增加行為到被包裝的對象上。

 

jdk中的裝飾者:

jdk中也有用到裝飾者模式的,那就是IO流中用到了。我想每個人學習IO流的時候都比較痛苦,可能不僅是要區(qū)分字節(jié)流和字符流,而且一些裝飾用的流也企圖混淆我們的視線。比如說BufferedInputStream就是一個裝飾流,可以用它裝飾FileInputStream,所以我們最常用的應(yīng)該是這樣的形式:new BufferedInputStream(new FileInputStream(new File(""))); 和我們上面講的類似,裝飾流也是增加一些行為到被裝飾的對象上,比如BufferedInputStream通過緩沖數(shù)組來提高性能,提供一個讀取整行的readLine方法來進行擴展。下面的圖能讓你更加了解IO流中的裝飾者:

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓

這圖上列出的是字節(jié)流,字符流也是類似的。但是裝飾流使IO中的類更多了,這有時會造成我們的困擾,如果非要說的話,這也算一個“缺點”;

 

自己寫例子

學習完書上的例子,我總是想著自己舉一個例子,可是要想一個符合的例子真的挺難的,這就是“書到用時方恨少”?哈哈,不扯了,看一下我自己想的例子:我要組裝一臺電腦,現(xiàn)在只有一個機箱,需要添加其他的配件,就是這里例子了。

 

動手組裝電腦

首先需要一個電腦的抽象類Computer,有型號type和價格price2個屬性,有獲得組成部分comprise()和計算總價prices()2個抽象方法:

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓 View Code

 

假如現(xiàn)在只有一個先馬的機箱作為被裝飾者SAMAChassis:

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓 View Code

 

現(xiàn)在要網(wǎng)機箱中加入CPU,主板,內(nèi)存等配件,將這些配件作為裝飾者裝飾到機箱上。需要一個裝飾者的抽象類DiyDecorator:

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓 View Code

 

這里設(shè)計到上面提到的一個問題,我把Computer的引用放到了抽象類DiyDecorator中以增加代碼的復用。同時也現(xiàn)實了comprise方法和prices方法,這樣,子類只需要調(diào)用父類的構(gòu)造方法即可。

說到這里,又想起來一個問題,我們知道不能創(chuàng)建抽象類的對象,那么,那么抽象類為什么有構(gòu)造方法呢?其實,從這個例子就可以看出,抽象類的構(gòu)造方法是用來實例化成員變量的。

 

具體的裝飾類CPU:

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓 View Code

其他的具體裝飾類我就不寫了,也不寫附錄了,很簡單。

 

測試一下DecoratorTest:

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓 View Code

 

小結(jié)一下

裝飾者模式動態(tài)地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。裝飾者模式符合開放-關(guān)閉原則:對擴展開放,對修改關(guān)閉。

 

 

附錄A

深焙咖啡DarkRoast

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓 View Code

 

低咖啡因咖啡Decaf

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓 View Code

 

綜合咖啡HouseBlend

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓 View Code

 

 

附錄B

豆?jié){Soy

移動開發(fā)培訓,Android培訓,安卓培訓,手機開發(fā)培訓,手機維修培訓,手機軟件培訓 View Code

 

奶泡Whip

http://www.cnblogs.com/pdzbokey/p/6560678.html