在談裝飾這模式之前,我想讓大家思考一下,我們開發的時候爲什麼要遵守開閉原則(對拓展開放,對修改關閉)?這看起來是一個非常矛盾的事情,又要拓展功能,又不能夠修改已有的代碼。其實,這樣做最主要的原因是防止因爲修改已有代碼引入新的BUG,已有的代碼一般都是經過檢測的,很少有BUG,如果直接在上面修改的話,很可能導致未知的錯誤,可能引起別的組件的故障,甚至癱瘓整個系統。
在明白上述這一點之後,我們就比較容易理解那些明明兩三行代碼就能完成的功能,爲什麼要在上面做各種抽象了,主要是爲了三個方面:
- 代碼重用(從更長遠的角度看起來,簡化開發)
- 易於拓展(增加功能方便)
- 便於維護(不修改已有代碼,結構層次清晰)
OK,下面進入正題
我們假設以下情景:有一家DIY奶茶廠商,要管理很多飲品,不同的配方就是一種產品,假設主料有茶,咖啡等等,配料有紅豆,糖,牛奶,等等,我們要科學的對這些材料進行管理,按照面向對象的思維,每一種產品就是一個類,我的天,無數的組合豈不是無窮無盡的類,這樣設計顯然是不科學的。我們這時候可以用裝飾者模式,我們看看裝飾者模式是如何做的
我們先來觀察這些產品有何特點:都是由主料和多種輔料複合而成的,這時候我們就要想辦法,如何動態的去給主料裏添加輔料,而不是在編譯的時候直接寫死。
先上類圖
我們把主料和輔料裝飾者都實現Beverage接口,這樣做的作用最後再說。下面我們來看看代碼
public interface Beverage {
String discription();
float cost();
}
奶茶的代碼和咖啡一樣,我就不重複了
public class Coffee implements Beverage{
public String discription() {
return "咖啡";
}
public float cost() {
return 5;
}
}
//抽象方法,啥也沒做
public abstract class IngredientsDecorator implements Beverage{
}
下面幾個輔料的方法也幾乎一致,我就只寫一個了
public class Pudding extends IngredientsDecorator{
//被裝飾對象
Beverage beverage;
//要裝飾哪個對象就傳哪個
public Pudding(Beverage beverage){
this.beverage=beverage;
}
//增強描述功能
public String discription() {
return beverage.discription()+",布丁";
}
//增強付款功能
public float cost() {
return beverage.cost()+2;
}
}
我們寫個測試代碼
public static void main(String[] args) {
Coffee coffee=new Coffee();
Ice coffeeIce=new Ice(coffee);
Sugar cISugar=new Sugar(coffeeIce);
System.out.println(cISugar.cost());
System.out.println(cISugar.discription());
}
輸出:
6.0
咖啡,冰,糖
我們來看看之前留下的問題,爲什麼要裝飾者要實現Beverage類?
public Pudding(Beverage beverage){
this.beverage=beverage;
}
看看這個代碼,其實就是爲了這段代碼,裝飾對象可以不斷的被裝飾,也就實現了加各種輔料
這裏說一下繼承,繼承不僅僅是爲了代碼重用,還有一個作用就是保持類型的一致,我們裝飾者模式主要就是利用了後面這一點
我們可以看到,用這種DIY組合的方式就能達到我們的需求,就不用寫那麼多的類了
但是裝飾者模式也有不少的缺點:
- 可能裝飾類會非常多
- 如果被裝飾對象經過多次裝飾,可能會導致結構非常複雜
- 如果需要的對象需要進過多次裝飾,那麼人爲的去組裝是非常難以保證正確性的
其實對於複雜的裝飾,我們一般會採用工廠模式去進行裝飾,這樣就不必去關注這些實現細節了
裝飾者模式是一種非常常見的模式,他能在不修改源代碼的情況下,動態的給原對象賦予新的功能,最常見的IO類就大量使用了裝飾者模式
PS:源碼地址