1. 裝飾器模式
1.1 簡(jiǎn)介
??Decorator模式就是在不改變?cè)愇募褪褂美^承的情況下,動(dòng)態(tài)的擴(kuò)展一個(gè)對(duì)象的功能。這些功能需要由用戶動(dòng)態(tài)決定加入的方式和時(shí)機(jī)墓猎,Decorator提供了"即插即用"的方法胡诗,在運(yùn)行期間決定何時(shí)增加何種功能邓线。
??通過創(chuàng)建一個(gè)包裝對(duì)象(裝飾)淌友,來包裹真實(shí)的對(duì)象。軟件開發(fā)的某個(gè)階段和裝修房子像極了骇陈!系統(tǒng)的基本功能實(shí)現(xiàn)后震庭,需要完善功能和修飾界面,但基本功能和流程框架不會(huì)做大的改動(dòng)你雌。房屋的裝修器联,是在毛胚房的基礎(chǔ)上層層 wrapper(包裝),先刷刷墻面漆婿崭,再鋪鋪木地板拨拓,再購置家具布置一下等,可以看作是對(duì)毛胚房層層包裝氓栈。所以Decorator 設(shè)計(jì)模式也被稱為 Wrapper 設(shè)計(jì)模式渣磷。
1.2
??Decorator 設(shè)計(jì)模式正如毛胚房的裝修,不會(huì)改變?cè)叻康幕究蚣懿疲皇窃黾有碌耐庥^幸海、功能等,且隨著時(shí)間的推移奥务,可以不斷的實(shí)施裝修工程:增加新的家具物独、根據(jù)心情換換新鮮的墻紙等等。在面向?qū)ο蟮某绦蛟O(shè)計(jì)中氯葬,擴(kuò)展系統(tǒng)的原有功能也可以采用繼承挡篓、組合的方式。繼承也不會(huì)改變毛胚房(父類)帚称,但是由于裝修工程的復(fù)雜和很多不可預(yù)測(cè)的改變官研,比如不同墻紙和地板樣式的組合數(shù)量簡(jiǎn)直無法想想,難道我們要為每一種組合都定義一個(gè)子類嗎闯睹?顯然這是不現(xiàn)實(shí)的戏羽,即通過繼承的方式來應(yīng)對(duì)未來的功能和外觀改變通常是吃力不討好的事情。組合的方式也不可取楼吃,因?yàn)檫@要求不斷的修改父類的結(jié)構(gòu)始花,相當(dāng)于對(duì)毛胚房大動(dòng)干戈,房屋的可維護(hù)性和可靠性就大大降低了孩锡。
??讓我們回顧一下設(shè)計(jì)模式的重要原則:Classes should be open for extenstion, but closed for modification酷宵。Decorator 設(shè)計(jì)模式很好的詮釋了這個(gè)原則。
1.3 Decorator模式結(jié)構(gòu)
Decorator模式uml:
Decorator模式角色:
- Component為統(tǒng)一接口躬窜,也是裝飾類和被裝飾類的基本類型浇垦。
- ConcreteComponent為具體實(shí)現(xiàn)類,也是被裝飾類荣挨,他本身是個(gè)具有一些功能的完整的類男韧。
- Decorator是裝飾類朴摊,實(shí)現(xiàn)了Component接口的同時(shí)還在內(nèi)部維護(hù)了一個(gè)ConcreteComponent的實(shí)例,并可以通過構(gòu)造函數(shù)初始化煌抒。
- ConcreteDecorator是具體的裝飾產(chǎn)品類仍劈,每一種裝飾產(chǎn)品都具有特定的裝飾效果。
2. Decorator模式示例
??我們以毛胚房的裝修為例寡壮,我們可以在毛坯房的基礎(chǔ)上刷墻漆贩疙、鋪地板、安裝電器等等况既。
Component接口:
public interface Room {
public String showRoom();
}
毛坯房 ConcreteComponent:
public class BlankRoom implements Room {
@Override
public String showRoom() {
return "毛坯房";
}
}
虛擬裝修器RoomDecorator:
// 裝修工程的模板
abstract public class RoomDecorator implements Room {
// wrapper 的具體體現(xiàn)这溅,每個(gè)獨(dú)立的裝修工序都是在上一個(gè)裝修工序
// 的基礎(chǔ)上進(jìn)行的,裝修就是這樣層層包裝完成的
protected Room roomToBeDecorated;
public RoomDecorator(Room roomToBeDecorated) {
this.roomToBeDecorated = roomToBeDecorated;
}
@Override
public String showRoom() {
// 委托(delegate)
return roomToBeDecorated.showRoom();
}
}
PaintedDecorator:
public PaintedDecorator(Room roomToBeDecorated) {
super(roomToBeDecorated);
}
public String showRoom(){
doPainting();
return super.showRoom() + "刷墻漆";
}
// 刷墻漆
private void doPainting(){}
}
FlooredDecorator:
public class FlooredDecorator extends RoomDecorator {
public FlooredDecorator(Room roomToBeDecorated) {
super(roomToBeDecorated);
}
public String showRoom(){
doFlooring();
return super.showRoom() + "鋪地板";
}
// 鋪地板
private void doFlooring(){}
}
調(diào)用示例:
public static void main(String[] args) {
// 毛胚房
Room blankRoom = new BlankRoom();
// 刷了墻的毛胚房
Room paintedRoom = new PaintedDecorator(new BlankRoom());
// 先刷墻再鋪地板的毛胚房
// 注意到連續(xù)的 new 操作棒仍,這就是 wrapper悲靴,最內(nèi)層的一般是毛胚房
Room paintedAndFlooredRoom = new FlooredDecorator(new
PaintedDecorator(new BlankRoom()));
// 先鋪地板再刷墻的毛胚房
Room flooredAndPaintedRoom = new PaintedDecorator(new
FlooredDecorator(new BlankRoom()));
System.out.println(blankRoom.showRoom());
System.out.println(paintedRoom.showRoom());
System.out.println(paintedAndFlooredRoom.showRoom());
System.out.println(flooredAndPaintedRoom.showRoom());
}
Decorator模式優(yōu)點(diǎn):
- 裝飾器模式和繼承的共同特點(diǎn)就是擴(kuò)展對(duì)象的功能,而裝飾器模式比靜態(tài)繼承更加靈活.
- 通過使用不同的具體裝飾器類莫其,及其不同的排列組合癞尚,可以產(chǎn)生出大量不同的組合.
- 避免在層次結(jié)構(gòu)高層的類有太多的特征
- Decorator與其他的Component不一樣,Decorator說一個(gè)透明的包裝乱陡。
Decorator模式缺點(diǎn):
- 會(huì)出現(xiàn)一些小類(小程序)浇揩,過度使用會(huì)使程序變得復(fù)雜.
3. CDI 對(duì) Decorator模式的支持
??Decorator 設(shè)計(jì)模式雖然降低了需求變更對(duì)軟件開發(fā)的影響,但是通過層層包裝憨颠,即層層 new 操作創(chuàng)建對(duì)象的方式不夠優(yōu)雅胳徽。CDI 容器可以管理組件的生命周期,在大部分情況下我們無須通過 new 操作創(chuàng)建所需要的對(duì)象爽彤。CDI 中的 Decorator/Delegate 注解很大程度上簡(jiǎn)化了 Decorator 設(shè)計(jì)模式的代碼編寫量养盗,比如實(shí)現(xiàn)上面相同的功能,借助于 CDI适篙,就無須 RoomDecorator 這個(gè)抽象類了往核,所有的 Decorator 類直接實(shí)現(xiàn) Room 接口并使用注解聲明為 Decorator 即可,比如 PaintedDecorator 類:
** PaintedDecorator:**
import javax.decorator.Decorator;
import javax.decorator.Delegate;
import javax.inject.Inject;
@Decorator
public class PaintedDecorator implements Room {
@Inject
@Delegate
Room roomToBeDecorated;
public String showRoom(){
doPainting();
return roomToBeDecorated.showRoom() + "刷墻漆";
}
// 刷墻漆
private void doPainting(){}
}
** RoomController 中是這樣使用 Decorator 類的:**
import javax.inject.Inject;
import javax.inject.Named;
@Named("room")
public class RoomController {
@Inject
Room room;
public String showRoom(){
return room.showRoom();
}
}
??一切看起來簡(jiǎn)單多了嚷节,秘密就在于 CDI 的 beans.xml 文件铆铆,CDI 會(huì)根據(jù) beans.xml 文件中對(duì) Decorator 的聲明順序加載并構(gòu)造相應(yīng)的 Decorator 對(duì)象,完全復(fù)現(xiàn)了傳統(tǒng)方式的 Decorator 模式中通過 new 操作層層包裝“毛胚房”的過程丹喻。
beans.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
<!-- To activate CDI decorator, it must be specified below -->
<decorators>
<class>cn.edu.sdut.r314.PaintedDecorator</class>
<class>cn.edu.sdut.r314.FlooredDecorator</class>
</decorators>
</beans>
??完整的 CDI 版本的 Decorator 示例參見:https://github.com/subaochen/weld-tutorial,具體運(yùn)行方式參見其中的 README.md 文件翁都。
4. Decorator模式實(shí)際應(yīng)用
??在 Java IO API 中碍论,其實(shí)大量的使用了 Decorator 模式。試想一下柄慰,不同的輸入輸出源鳍悠,對(duì)輸入輸出數(shù)據(jù)的不同處理方式和不同流程税娜,Decorator 模式正是大顯身手的時(shí)候〔匮校基礎(chǔ)的輸入輸出類就好比是毛胚房敬矩,比如下面的用法:
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("filename"));
??在Java IO 模式圖中可以更清楚的了解 Java IO 是如何使用 Decorator 模式的。
Java IO 模式圖:
??根據(jù)Java IO模式圖中的Decorator 模式蠢挡,我們可以實(shí)現(xiàn)一個(gè) Java IO 的 Decorator弧岳,比如讀入一個(gè)文件,將每個(gè)字母的 ASCII 碼都后移一位业踏,代碼如下:
** EncodeInputStream:**
public class EncodeInputStream extends FilterInputStream {
protected EncodeInputStream(InputStream in) {
super(in);
}
public int read() throws IOException {
int c = super.read();
return c + 1;
}
}
** 調(diào)用示例:**
public class EncodeInputStream extends FilterInputStream {
public class InputTest {
public static void main(String[] args) {
int c;
try {
InputStream in = new EncodeInputStream(new
BufferedInputStream(InputTest.class.getResourceAsStream("test.txt")));
while ((c = in.read()) >= 0) {
System.out.print((char) c);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
??執(zhí)行后的輸出結(jié)果是(test.txt 文件的內(nèi)容是 hello,world!):ifmmp!xpsme"禽炬。Java IO 的 Decorator 在 CDI 環(huán)境下和傳統(tǒng)環(huán)境下寫法上沒有多大區(qū)別,只是一般在 CDI 環(huán)境下自己寫的 Decorator 可以當(dāng)作組件使用勤家,即可以通過 Decorator 的類型而不是名稱查詢組件而已腹尖,不再贅述。