裝飾者模式

設(shè)計(jì)模式

一、什么是裝飾模式

裝飾者模式動態(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)閉!

7017386-c75c5e327d5d481e.png

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

7017386-a96daaef68afe4a3.png
  1. Component,給出一個抽象接口或者抽象類,規(guī)范實(shí)現(xiàn)類的一些方法;
  2. ConcreteComponent:具體的一個組件,實(shí)現(xiàn)了抽象方法;
  3. Decorator:抽象的裝飾者,對抽象接口或者抽象類的一個引用,在 method 方法里面使用這個引用完成任務(wù);(代理模式需要實(shí)例化)
  4. 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):

  1. 裝飾對象和真實(shí)對象有相同的接口。這樣客戶端對象就能以和真實(shí)對象相同的方式和裝飾對象交互。
  2. 裝飾對象包含一個真實(shí)對象的引用(reference)。
  3. 裝飾對象接受所有來自客戶端的請求。它把這些請求轉(zhuǎn)發(fā)給真實(shí)的對象。
  4. 裝飾對象可以在轉(zhuǎn)發(fā)這些請求以前或以后增加一些附加功能。這樣就確保了在運(yùn)行時,不用修改給定對象的結(jié)構(gòu)就可以在外部增加附加的功能。

所以在以下情況下可以使用裝飾模式:

  1. 在不影響其他對象的情況下,以動態(tài)、透明的方式給單個對象添加職責(zé)。
  2. 需要動態(tài)地給一個對象增加功能,這些功能也可以動態(tài)地被撤銷。
  3. 當(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í)裝飾器模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容