設(shè)計(jì)模式-裝飾者模式(三)

概述

一般情況下,當(dāng)我們想給一個(gè)類或?qū)ο筇砑庸δ艿臅r(shí)候铺遂,有兩種常用的方式:

  1. 繼承:通過使用繼承己肮,我們可以使子類既能擁有父類的功能士袄,也能實(shí)現(xiàn)本身的功能。
  2. 組合:而組合的方式谎僻,是在某一個(gè)類中包裝另一個(gè)類的對(duì)象窖剑,然后通過這個(gè)對(duì)象引用來決定是否擁有該類的某些功能,我們把這個(gè)包裝的對(duì)象可以稱為裝飾器 (Decorator)戈稿;

由于繼承是一種靜態(tài)的行為西土,而組合則可以實(shí)現(xiàn)動(dòng)態(tài)的往一個(gè)類中添加的新的行為。并且就功能而言鞍盗,組合相比繼承更加靈活需了,這樣可以給某個(gè)對(duì)象而不是整個(gè)類添加一些功能跳昼。而裝飾者模式就是基于組合來實(shí)現(xiàn)的:

裝飾者模式是一種動(dòng)態(tài)地往一個(gè)類中添加新的行為的設(shè)計(jì)模式。

裝飾者模式簡介

??也就是說肋乍,通過使用裝飾者模式鹅颊,可以在運(yùn)行時(shí)擴(kuò)充一個(gè)類的功能。大概原理是:增加一個(gè)裝飾類包裝原來的類墓造,包裝的方式一般是通過在將原來的對(duì)象作為裝飾類的構(gòu)造函數(shù)的參數(shù)堪伍。裝飾類實(shí)現(xiàn)新的功能,但是觅闽,在不需要用到新功能的地方帝雇,它可以直接調(diào)用原來的類中的方法。修飾類必須和原來的類有相同的接口蛉拙。
??修飾模式是類繼承的另外一種選擇尸闸。類繼承在編譯時(shí)候增加行為,而裝飾模式是在運(yùn)行時(shí)增加行為孕锄。
??當(dāng)有幾個(gè)相互獨(dú)立的功能需要擴(kuò)充時(shí)吮廉,這個(gè)區(qū)別就變得很重要。在有些面向?qū)ο蟮木幊陶Z言中畸肆,類不能在運(yùn)行時(shí)被創(chuàng)建宦芦,通常在設(shè)計(jì)的時(shí)候也不能預(yù)測(cè)到有哪幾種功能組合。這就意味著要為每一種組合創(chuàng)建一個(gè)新類轴脐。相反踪旷,裝飾者模式是面向運(yùn)行時(shí)候的對(duì)象實(shí)例的,這樣就可以在運(yùn)行時(shí)根據(jù)需要進(jìn)行組合。

結(jié)構(gòu)

我們先通過一張圖來看下裝飾者模式的結(jié)構(gòu):

Decorator.jpg

圖片來源:圖說設(shè)計(jì)模式 - 裝飾模式 - 結(jié)構(gòu)圖解
通過以上結(jié)構(gòu)豁辉,我們可以大概了解到裝飾者模式的幾個(gè)角色:

  1. Component,最基礎(chǔ)的底層接口舀患,包含基礎(chǔ)的功能方法徽级;
  2. ConcreteComponent ,底層接口Component原始的實(shí)現(xiàn)聊浅,我們要裝飾的對(duì)象就是這部分餐抢,這部分也就是被裝飾者;
  3. Decorator低匙,裝飾角色旷痕,一般情況下該對(duì)象是一個(gè)抽象類,實(shí)現(xiàn)自Component顽冶,在該類中一般會(huì)有一個(gè)指向Component接口的對(duì)象欺抗,指向被裝飾的對(duì)象,通過組合的形式對(duì)其進(jìn)行包裝强重;
  4. ConcreteDecorator绞呈,具體的裝飾角色贸人,繼承自Decorator缩宜,對(duì)具體的被裝飾者進(jìn)行包裝走触,并且可以添加新的功能;

我們先通過簡單的代碼來看一下:

  1. Component接口:
public interface Component {
    /**
     * 底層基礎(chǔ)接口
     */
    void operation();
}
  1. ConcreteComponent實(shí)現(xiàn)類:
public class ConcreteComponent implements Component {
    /**
     * 基礎(chǔ)接口的實(shí)現(xiàn)
     */
    @Override
    public void operation() {
        System.out.println("concrete component");
    }
}
  1. Decorator 裝飾角色褐筛,抽象類圾亏,實(shí)現(xiàn)Component:
public abstract class Decorator implements Component {
    /** 維護(hù)一個(gè)被裝飾的對(duì)象的引用*/
    protected Component component;

    /**
     * 通過構(gòu)造方法傳入被裝飾的對(duì)象
     * @param component
     */
    public Decorator(Component component) {
        this.component = component;
    }

    /**
     * 調(diào)用被裝飾的對(duì)象的方法
     */
    @Override
    public void operation() {
        component.operation();
    }
}
  1. ConcreteDecorator 具體裝飾角色十拣,繼承自Decorator抽象類:
public class ConcreteDecorator extends Decorator {
    /**
     * 自定義自己的屬性
     */
    private String addState = "test";

    public ConcreteDecorator(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        component.operation();
        System.out.println("addState:" + addState);
    }

    /**
     * 自定義新的方法,實(shí)現(xiàn)新的功能
     */
    public void addedBehavior() {
        System.out.println("addState:" + addState);
    }
    // 省略掉get,set方法
}
  1. Main志鹃,用于測(cè)試:
public class Main {
    public static void main(String[] args) {
        Component component = new ConcreteDecorator(new ConcreteComponent());
        component.operation();
    }
}
優(yōu)缺點(diǎn)
  1. 我們先來看下優(yōu)點(diǎn):

a. 裝飾者模式可以用來代替繼承關(guān)系夭问,可以動(dòng)態(tài)的擴(kuò)展一個(gè)實(shí)現(xiàn)類的功能,且不影響到其他對(duì)象弄跌;
b. 裝飾者模式不管最終裝飾了多少層甲喝,最終返回的對(duì)象還是Component;
c. 遵循設(shè)計(jì)模式的原則:對(duì)擴(kuò)展開放铛只,對(duì)修改關(guān)閉埠胖。

  1. 有優(yōu)點(diǎn)就免不了會(huì)有缺點(diǎn),我們?cè)賮砜聪氯秉c(diǎn)淳玩,缺點(diǎn)的話其實(shí)就比較明顯了:

裝飾者模式采用組合的形式比繼承靈活直撤,但也比繼承相對(duì)復(fù)雜,如果裝飾的層數(shù)過多蜕着,出現(xiàn)問題排查的時(shí)候也會(huì)相對(duì)困難些谋竖;因此,盡量減少裝飾類的數(shù)量承匣,以便降低系統(tǒng)的復(fù)雜度蓖乘;

適用場(chǎng)景

裝飾者模式的適用場(chǎng)景可以根據(jù)它的優(yōu)點(diǎn)來進(jìn)行選擇:

  1. 當(dāng)需要在不影響其他對(duì)象的情況下,動(dòng)態(tài)的為一個(gè)類擴(kuò)展功能的時(shí)候韧骗;
  2. 當(dāng)不能使用繼承或者不適合采用繼承的方式進(jìn)行類的擴(kuò)展和維護(hù)時(shí)(比如類定義為final類型)嘉抒,可以采用裝飾者模式;
在JAVA I/O流中的應(yīng)用

??而裝飾者模式在Java中應(yīng)用最廣泛最出名的恐怕就是Java中的I/O的API了袍暴。如果我們?cè)趯W(xué)習(xí)I/O體系前些侍,沒有了解過裝飾者模式的話,那么由于I/O體系本身復(fù)雜的結(jié)構(gòu)政模,學(xué)習(xí)的時(shí)候或許會(huì)有一種很頭疼的感覺岗宣。我們還是先來看下I/O體系大致結(jié)構(gòu):


I/O體系結(jié)構(gòu).jpg

注:沒有完全展示所有I/O相關(guān)的接口。 圖片來源:google 圖片
我們可以大致把上圖分為幾層:

  1. Reader淋样、Writer耗式、InputStream、OutputStream,對(duì)應(yīng)裝飾者角色中最基礎(chǔ)的接口:Component纽什,這里面包含了基礎(chǔ)的操作措嵌;
  2. FileInputStream,F(xiàn)ileOutputStream等第二層沒有其他實(shí)現(xiàn)類的類芦缰,對(duì)應(yīng)于裝飾者中被裝飾的角色:ConcreteComponent企巢;
  3. FilterReader,F(xiàn)ilterOutputStream等让蕾,對(duì)應(yīng)于裝飾者中的抽象裝飾角色:Decorator浪规;
  4. BufferedOutputStream,DataOutputStream等探孝,對(duì)應(yīng)于裝飾者中的具體裝飾角色:ConcreteDecorator笋婿;

我們接下來可以通過一個(gè)代碼簡單看下實(shí)現(xiàn):

public class Main {
    public static void main(String[] args) throws Exception {
        // 1. 定義輸入流
        InputStream inputStream = null;
        try {
            // 2. 實(shí)例 被裝飾者 : 文件讀取
            inputStream = new FileInputStream("E://test.txt");
            // 3. 具體裝飾角色:緩沖流
            inputStream = new BufferedInputStream(inputStream);
            // 4. 進(jìn)行操作:開始讀文件
            byte[] text = new byte[inputStream.available()];
            inputStream.read(text);
            System.out.println(new String(text));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            inputStream.close();
        }
    }
}

當(dāng)然,裝飾者模式在Java中應(yīng)用的地方還有許多地方顿颅,比如最近學(xué)習(xí)的Mybatis中Executor體系結(jié)構(gòu):Executor, BaseExecutor, CachingExecutor等缸濒。

總結(jié)

學(xué)習(xí)了裝飾者模式之后,我們來簡單回顧總結(jié)下:

  1. 裝飾者模式可以動(dòng)態(tài)的擴(kuò)展類的功能粱腻,就這點(diǎn)來說庇配,裝飾者模式要比使用繼承更為靈活,按照GoF設(shè)計(jì)模式的劃分绍些,裝飾者模式屬于結(jié)構(gòu)型模式的一種捞慌。
  2. 裝飾者模式遵循了對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉的設(shè)計(jì)原則柬批,在不修改原有代碼的基礎(chǔ)上啸澡,通過組合的形式擴(kuò)展新的功能。
  3. 裝飾者模式雖然比繼承的形式要靈活氮帐,但由于其本身的繁瑣性嗅虏,所以也會(huì)相對(duì)繼承復(fù)雜些,如果裝飾的層數(shù)過多上沐,一旦出現(xiàn)問題皮服,將會(huì)提高我們排查問題的難度,所以要盡量減少裝飾的層數(shù)奄容,以便降低系統(tǒng)的復(fù)雜度。

本文參考自:
裝飾器模式--繼承的另一個(gè)選擇
維基百科-裝飾模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末产徊,一起剝皮案震驚了整個(gè)濱河市昂勒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舟铜,老刑警劉巖戈盈,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡塘娶,警方通過查閱死者的電腦和手機(jī)归斤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刁岸,“玉大人脏里,你說我怎么就攤上這事『缡铮” “怎么了迫横?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長酝碳。 經(jīng)常有香客問我矾踱,道長,這世上最難降的妖魔是什么疏哗? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任呛讲,我火速辦了婚禮,結(jié)果婚禮上返奉,老公的妹妹穿的比我還像新娘贝搁。我一直安慰自己,他們只是感情好衡瓶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布徘公。 她就那樣靜靜地躺著,像睡著了一般哮针。 火紅的嫁衣襯著肌膚如雪关面。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天十厢,我揣著相機(jī)與錄音等太,去河邊找鬼。 笑死蛮放,一個(gè)胖子當(dāng)著我的面吹牛缩抡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播包颁,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瞻想,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了娩嚼?” 一聲冷哼從身側(cè)響起蘑险,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岳悟,沒想到半個(gè)月后佃迄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泼差,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年呵俏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了堆缘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡普碎,死狀恐怖吼肥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情随常,我是刑警寧澤潜沦,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站绪氛,受9級(jí)特大地震影響唆鸡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜枣察,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一争占、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧序目,春花似錦臂痕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至叛赚,卻和暖如春澡绩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背俺附。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國打工肥卡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人事镣。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓步鉴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親璃哟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子氛琢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容