緣由
沒什麼特别的,之前看懂了,這次自己再複述一下。畢竟把别人講懂了才是真的懂了。主要參考了head first 設計模式。
書中例子
面和具體的面
例子講述的是在為星巴克咖啡的制作訂單的情況,比如客人點了飲料,那麼系統會自動算出價格(不知道是我沒有體會到,還是這個例子不太合适,算出價格那麼簡單的事還需要用到類?,不過不影響我們思考裝飾者模式)。不過似乎星巴克離普通的中國人還是太遙遠了,我倒可以認為我們想象成中國面館比較好。
這家中國面館賣很多種類的面,有我喜歡的哨子面、牛肉面、雜醬面、闆面等等面,每一個面都有價格,這是非常正常的。如果每一種面都有一個價格,那麼這個訂單生成系統好像還比較簡單。因為繼承是一種不錯的選擇。下圖直接截取了head first 設計模式,是以裡面是飲料。那麼飲料就相當于我上面所述的面,而牛肉面就相當于一個飲料的一種,比如:Dark Roast。
加了配料的面
問題來了,面還可以加各類配料,比如你說老闆多加一個雞蛋,另一個說多加一個肉丸,多加一個火腿腸。難道我們為每一種加了配料的面都來實作一種類來計算價錢嗎?如此一來,将會有非常多的類。比如加了雞蛋的牛肉面、加了肉丸的牛肉面.....那麼我們的類就繼承就會成為下面這個樣子。
可以看出我們要實作每一個子類就非常辛苦了,需要自己懂把加了丸子、雞蛋的牛肉面,提前加一遍算好放在這個子類裡使用。更可怕的是:如果丸子價格漲了,怎麼辦?我們又要把所有的有丸子的子類都加一遍嗎?
将調料試着執行個體變量
這是一種可以解決的方案。在父類面裡面有丸子、雞蛋的布爾值,和具體價格,那麼使用者在選擇添加了雞蛋的時候,就将雞蛋的布爾值改為true,然後再将父類的cost()方法實作為加上所有的配料的價格。那麼子類在cost()方法裡面用自己這道面的價格加上父類的cost(),就是配料的價格,那麼就算出了總共需要多少錢了。
下圖是僞代碼,我們可以看出,子類DarkRoast的cost()方面裡面調用了父類的cost()方法,而父類cost()方面,主要是判斷這個訂單有沒有加這個某種配料,加了的話就加上這個配料的價格。
然而,這樣做有以下三種情況會使我們很難處理:
- 如果出現新調料,我們需要改變超類。似乎這樣很不好
- 某些飲料裡面就不能加某些調料,然而我們并沒有限制
- 如果某個客戶想要雙倍的調料,則無法處理。
這裡書中: 提出了類應該對擴充開放,對修改關閉。
因為
- 對類修改也許會引入額外的bug,那麼上面的設計也許會使得我們修改超類,這樣非常不好。
- 而如果我們有需要,可以通過對類進行擴充來完成。
引入裝飾者模式
此時書中引入裝飾者模式,其實我是知道看來了代碼之後才知道到底是怎麼回事。我的了解就是,所謂裝飾者,其也是繼承于基類(面)的一個實作類,這個實作類必須有有有一個成員變量:就是另一個實作類,也就是被包含的實作類,也就是我們裝飾者所要裝飾的對象。比如牛肉面就是一個實作類,他是被裝飾的,可以被加雞蛋這個實作類來修飾,加雞蛋這個實作類的成員變量可以引用牛肉面,那麼就組成了加雞蛋的牛肉面,如果其成員變量引用了哨子面,那麼就成了加雞蛋的哨子面。而且,這個加雞蛋的哨子面還可以被加丸子的實作類來裝飾,那麼就是成了加丸子 加雞蛋的牛肉面。當然,也可以加兩個雞蛋。
是以,我們要把實作類分為兩類,一種實作類時調料,其有一個成員變量就是基類的一個對象。另一個實作類就是面的種類:牛肉面、哨子面。
我在書上的圖,作了一些額外的說明。如下:
另外書上也給了飲料的圖,更為簡單和清晰些:
代碼
實際上我還是覺得代碼描述的更為清楚:
public class TestMain {
public static void main(String[] args) {
//普通牛肉面
Noodle beefNoodle = new BeefNoodle();
System.out.println(beefNoodle.getDescription() + ":" + beefNoodle.cost());
//普通豬肉面
Noodle porkNoodle = new PorkNoodle();
System.out.println(porkNoodle.getDescription() + ":" + porkNoodle.cost());
//加了雞蛋的牛肉面
Noodle beefNoodle2 = new BeefNoodle();
beefNoodle2 = new EggCondiment(beefNoodle2);
System.out.println(beefNoodle2.getDescription() + ":" + beefNoodle2.cost());
//加了雞蛋和丸子的豬肉面
Noodle porkNoodle2 = new PorkNoodle();
porkNoodle2 = new EggCondiment(porkNoodle2);
porkNoodle2 = new MeatballCondiment(porkNoodle2);
System.out.println(porkNoodle2.getDescription() + ":" + porkNoodle2.cost());
}
}
/**
* 這是一個面的基類。是以面的種類的實作類和調料的裝飾類都必須繼承這個類。
* @author zy
*
*/
public abstract class Noodle {
String description = "unknown noodle";
public String getDescription(){
return description;
}
public abstract double cost();
}
public class BeefNoodle extends Noodle {
public BeefNoodle() {
// TODO Auto-generated constructor stub
description = "牛肉面";
}
@Override
public double cost() {
return 10;//表示這個碗牛肉面10元,當然可以做的更專業一些,用個字段來設定。
}
}
public class PorkNoodle extends Noodle {
public PorkNoodle() {
// TODO Auto-generated constructor stub
description = "豬肉面";
}
@Override
public double cost() {
// TODO Auto-generated method stub
return 7;
}
}
public abstract class CondimenDecorator extends Noodle {
/**
* 用于裝飾的實作類必須有一個成員變量就是要裝飾的對象。在這裡就是面
* 使用Noodle這個基類的原因除了一般的表示種類的面
* 用于裝飾的實作類,還可以再次被裝飾
* 有點像加雞蛋、加丸子的牛肉面
* 被裝飾了兩次
*
* 此外,書上講這個成員變量寫在了每一個實作類裡面,難道不能寫在這個父類裡面麼?
* 試試看。
*/
Noodle noodle;
/**
* 是以調料必須實作這個方法,這樣才能知道這個面的具體描述
* 比如 加雞蛋的牛肉面
*/
public abstract String getDescription();
}
public class EggCondiment extends CondimenDecorator {
public EggCondiment(Noodle noodle) {
// TODO Auto-generated constructor stub
this.noodle = noodle;
}
@Override
public String getDescription() {
// TODO Auto-generated method stub
return noodle.getDescription() + "加雞蛋";
}
@Override
public double cost() {
// TODO Auto-generated method stub
return noodle.cost() + 1;
}
}
public class MeatballCondiment extends CondimenDecorator {
public MeatballCondiment(Noodle noodle) {
// TODO Auto-generated constructor stub
this.noodle = noodle;
}
@Override
public String getDescription() {
// TODO Auto-generated method stub
return noodle.getDescription() + "加肉丸";
}
@Override
public double cost() {
// TODO Auto-generated method stub
return noodle.cost() + 2;
}
}
運作結果
牛肉面:10.0
豬肉面:7.0
牛肉面加雞蛋:11.0
豬肉面加雞蛋加肉丸:10.0
java的IO類實作了裝飾者模式
如下圖所示:
其中我們可以看出FilterInputStream專門作為裝飾者。其實作類LineNumberInputStream就可以得到InputStream流中的行數。
書中還舉出了簡單的例子,自己寫了一個FilterInputStream的實作類。也就是裝飾類。有新興趣的看看書吧
總結
比較有趣,不過這些設計模式都需要深刻的體會。懂了其含義隻是剛剛開始吧。對了,定義很難了解。是以,一直沒說。
源代碼
沒有上傳網盤。代碼比較簡單。