一、什么是裝飾模式
裝飾者模式動態(tài)地將責(zé)任附加到對象上。若要擴(kuò)展功能,裝飾者提供了比繼承更有彈性的替代方案。
和代理模式很相似,但在對被裝飾的對象的控制程度是不同的;裝飾者模式是對對象功能的加強(qiáng),而代理模式是對對象施加控制,并不提供對對象本身功能的加強(qiáng)。
二、為什么要用該模式
通過繼承的方式可以使子類具有父類的屬性和方法。
子類繼承父類后,因?yàn)橐恍I(yè)務(wù)需求可以通過重寫的方式來加強(qiáng)父類的方法的一些功能,也可以重新定義某些屬性,即覆蓋父類的原有屬性和方法,使其獲得與父類不同的功能。
而裝飾者模式的最基本的功能就是對傳入的一個對象進(jìn)行功能的加強(qiáng)與優(yōu)化。
那么問題來了,既然繼承方式也可以對已有類或?qū)ο筮M(jìn)行加強(qiáng),那為什么還要衍生出裝飾者模式這一思想呢?
裝飾者模式的意圖定義為:動態(tài)地給一個對象添加一些額外的職責(zé)。裝飾者模式存在的更重要的意義就在于動態(tài)的為對象添加一些功能(或分配額外職責(zé))。
一般的,我們?yōu)榱藬U(kuò)展一個類經(jīng)常使用繼承方式實(shí)現(xiàn),由于繼承為類引入靜態(tài)特征,并且隨著擴(kuò)展功能的增多,子類會很膨脹。
在不想增加很多子類的情況下擴(kuò)展類,如何實(shí)現(xiàn)呢?這時就要用到今天的主角:裝飾者模式了。
Java中的IO機(jī)制就用到了裝飾者模式。比如最常用的語句:
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filepath)));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
這就是最常見的裝飾者模式了,通過BufferedReader對已有對象FileReader的功能進(jìn)行加強(qiáng)和優(yōu)化。其實(shí)它不僅可以加強(qiáng)FileReader,所有的字符輸入流都可以通過這種方式進(jìn)行包裝。
將所有的字符輸入流抽象出了一個基類或接口即Reader,然后通過構(gòu)造方法的形式將Reader傳遞給BufferedReader,此時BufferedReader就可以對所有的字符輸入流進(jìn)行攔截和優(yōu)化了。
如果采用繼承機(jī)制,每個XXXReader就要衍生出一個BufferedXXXReader,再加上字符輸出流和字節(jié)輸入輸出流,那么Java的IO體系結(jié)構(gòu)該是多么的臃腫不堪??!而裝飾者模式的出現(xiàn)解決了這個問題,并且,裝飾者的出現(xiàn)也再一次的證明了面向?qū)ο蟮脑O(shè)計(jì)原則:多用組合,少用繼承!對擴(kuò)展開放,對修改關(guān)閉!

三、模式的結(jié)構(gòu)

- Component,給出一個抽象接口或者抽象類,規(guī)范實(shí)現(xiàn)類的一些方法;
- ConcreteComponent:具體的一個組件,實(shí)現(xiàn)了抽象方法;
- Decorator:抽象的裝飾者,對抽象接口或者抽象類的一個引用,在 method 方法里面使用這個引用完成任務(wù);(代理模式需要實(shí)例化)
- ConcreteDecorator:具體的裝飾者,對抽象裝飾者的抽象部分進(jìn)行實(shí)現(xiàn)。
第一步:抽象組件
/**
* 抽象組件
*
* Created by w1992wishes on 2017/10/30.
*/
public abstract class Component {
public abstract void method();
}
第二步:具體組件
/**
* 具體組件
*
* Created by w1992wishes on 2017/10/30.
*/
public class ConcreteComponent extends Component {
public void method() {
System.out.println("work");
}
}
第三步:抽象裝飾者
/**
* 抽象的裝飾者
*
* Created by w1992wishes on 2017/10/30.
*/
public abstract class Decorator extends Component{
private Component component;
public Decorator(Component component){
this.component = component;
}
@Override
public void method(){
beforeM();
component.method();
afterM();
}
public abstract void beforeM();
public abstract void afterM();
}
第四步:具體的抽象者
/**
* 具體的裝飾者
*
* Created by w1992wishes on 2017/10/30.
*/
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component){
super(component);
}
@Override
public void beforeM() {
System.out.println("Relax, first having a game before work!");
}
public void afterM() {
System.out.println("Relax, first having a game after work!");
}
}
第五步:客戶端運(yùn)行
/**
* Created by w1992wishes on 2017/10/30.
*/
public class Client {
public static void main(String[] args) {
Component c = new ConcreteComponent();
Decorator d = new ConcreteDecorator(c);
d.method();
}
}
結(jié)果:
Relax, first having a game before work!
work
Relax, first having a game after work!
四、優(yōu)點(diǎn)和缺點(diǎn)
4.1、優(yōu)點(diǎn):
- 裝飾模式與繼承關(guān)系的目的都是要擴(kuò)展對象的功能,但是裝飾模式可以提供比繼承更多的靈活性。
- 可以通過一種動態(tài)的方式來擴(kuò)展一個對象的功能,通過配置文件可以在運(yùn)行時選擇不同的裝飾器,從而實(shí)現(xiàn)不同的行為。
- 通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創(chuàng)造出很多不同行為的組合。可以使用多個具體裝飾類來裝飾同一對象,得到功能更為強(qiáng)大的對象。
- 具體構(gòu)件類與具體裝飾類可以獨(dú)立變化,用戶可以根據(jù)需要增加新的具體構(gòu)件類和具體裝飾類,在使用時再對其進(jìn)行組合,原有代碼無須改變,符合“開閉原則”。
4.2 缺點(diǎn):
- 使用裝飾模式進(jìn)行系統(tǒng)設(shè)計(jì)時將產(chǎn)生很多小對象,這些對象的區(qū)別在于它們之間相互連接的方式有所不同,而不是它們的類或者屬性值有所不同,同時還將產(chǎn)生很多具體裝飾類。這些裝飾類和小對象的產(chǎn)生將增加系統(tǒng)的復(fù)雜度,加大學(xué)習(xí)與理解的難度。
- 這種比繼承更加靈活機(jī)動的特性,也同時意味著裝飾模式比繼承更加易于出錯,排錯也很困難,對于多次裝飾的對象,調(diào)試時尋找錯誤可能需要逐級排查,較為煩瑣。
五、 適用環(huán)境
因?yàn)檠b飾者模式的以下特點(diǎn):
- 裝飾對象和真實(shí)對象有相同的接口。這樣客戶端對象就能以和真實(shí)對象相同的方式和裝飾對象交互。
- 裝飾對象包含一個真實(shí)對象的引用(reference)。
- 裝飾對象接受所有來自客戶端的請求。它把這些請求轉(zhuǎn)發(fā)給真實(shí)的對象。
- 裝飾對象可以在轉(zhuǎn)發(fā)這些請求以前或以后增加一些附加功能。這樣就確保了在運(yùn)行時,不用修改給定對象的結(jié)構(gòu)就可以在外部增加附加的功能。
所以在以下情況下可以使用裝飾模式:
- 在不影響其他對象的情況下,以動態(tài)、透明的方式給單個對象添加職責(zé)。
- 需要動態(tài)地給一個對象增加功能,這些功能也可以動態(tài)地被撤銷。
- 當(dāng)不能采用繼承的方式對系統(tǒng)進(jìn)行擴(kuò)充或者采用繼承不利于系統(tǒng)擴(kuò)展和維護(hù)時。不能采用繼承的情況主要有兩類:第一類是系統(tǒng)中存在大量獨(dú)立的擴(kuò)展,為支持每一種組合將產(chǎn)生大量的子類,使得子類數(shù)目呈爆炸性增長;第二類是因?yàn)轭惗x不能繼承(如final類)。
六、總結(jié)
- 裝飾模式用于動態(tài)地給一個對象增加一些額外的職責(zé),就增加對象功能來說,裝飾模式比生成子類實(shí)現(xiàn)更為靈活。它是一種對象結(jié)構(gòu)型模式。
- 裝飾模式包含四個角色:抽象構(gòu)件定義了對象的接口,可以給這些對象動態(tài)增加職責(zé)(方法);具體構(gòu)件定義了具體的構(gòu)件對象,實(shí)現(xiàn)了在抽象構(gòu)件中聲明的方法,裝飾器可以給它增加額外的職責(zé)(方法);抽象裝飾類是抽象構(gòu)件類的子類,用于給具體構(gòu)件增加職責(zé),但是具體職責(zé)在其子類中實(shí)現(xiàn);具體裝飾類是抽象裝飾類的子類,負(fù)責(zé)向構(gòu)件添加新的職責(zé)。
- 使用裝飾模式來實(shí)現(xiàn)擴(kuò)展比繼承更加靈活,它以對客戶透明的方式動態(tài)地給一個對象附加更多的責(zé)任。裝飾模式可以在不需要創(chuàng)造更多子類的情況下,將對象的功能加以擴(kuò)展。
- 裝飾模式的主要優(yōu)點(diǎn)在于可以提供比繼承更多的靈活性,可以通過一種動態(tài)的 方式來擴(kuò)展一個對象的功能,并通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創(chuàng)造出很多不同行為的組合,而且具體構(gòu)件類與具體裝飾類可以獨(dú)立變化,用戶可以根據(jù)需要增加新的具體構(gòu)件類和具體裝飾類;其主要缺點(diǎn)在于使用裝飾模式進(jìn)行系統(tǒng)設(shè)計(jì)時將產(chǎn)生很多小對象,而且裝飾模式比繼承更加易于出錯,排錯也很困難,對于多次裝飾的對象,調(diào)試時尋找錯誤可能需要逐級排查,較為煩瑣。
- 裝飾模式適用情況包括:在不影響其他對象的情況下,以動態(tài)、透明的方式給 單個對象添加職責(zé);需要動態(tài)地給一個對象增加功能,這些功能也可以動態(tài)地被撤銷;當(dāng)不能采用繼承的方式對系統(tǒng)進(jìn)行擴(kuò)充或者采用繼承不利于系統(tǒng)擴(kuò)展和維護(hù)時。
- 裝飾模式可分為透明裝飾模式和半透明裝飾模式:在透明裝飾模式中,要求客 戶端完全針對抽象編程,裝飾模式的透明性要求客戶端程序不應(yīng)該聲明具體構(gòu)件類型和具體裝飾類型,而應(yīng)該全部聲明為抽象構(gòu)件類型;半透明裝飾模式允許用戶在客戶端聲明具體裝飾者類型的對象,調(diào)用在具體裝飾者中新增的方法。
裝飾器模式相對于簡單的組合關(guān)系,還有兩個比較特殊的地方。
第一個比較特殊的地方是:裝飾器類和原始類繼承同樣的父類,這樣我們可以對原始類“嵌套”多個裝飾器類
第二個比較特殊的地方是:裝飾器類是對功能的增強(qiáng),這也是裝飾器模式應(yīng)用場景的一個重要特點(diǎn)。
實(shí)際上,符合“組合關(guān)系”這種代碼結(jié)構(gòu)的設(shè)計(jì)模式有很多,比如之前講過的代理模式、橋接模式,還有現(xiàn)在的裝飾器模式。盡管它們的代碼結(jié)構(gòu)很相似,但是每種設(shè)計(jì)模式的意圖是不同的。
代理模式中,代理類附加的是跟原始類無關(guān)的功能,而在裝飾器模式中,裝飾器類附加的是跟原始類相關(guān)的增強(qiáng)功能。
實(shí)際上,DataInputStream 也存在跟 BufferedInputStream 同樣的問題。為了避免代碼重復(fù),Java IO 抽象出了一個裝飾器父類 FilterInputStream,代碼實(shí)現(xiàn)如下所示。InputStream 的所有的裝飾器類(BufferedInputStream、DataInputStream)都繼承自這個裝飾器父類。這樣,裝飾器類只需要實(shí)現(xiàn)它需要增強(qiáng)的方法就可以了,其他方法繼承裝飾器父類的默認(rèn)實(shí)現(xiàn)。
參考
設(shè)計(jì)模式--裝飾者模式
50 | 裝飾器模式:通過剖析Java IO類庫源碼學(xué)習(xí)裝飾器模式