設計模式--裝飾者模式

設計原則:

  1. 少用繼承,多用組合
  2. 類應該對擴展開放,對修改關閉

目錄

本文的結構如下:

  • 什么是裝飾者模式
  • 為什么要用該模式
  • 模式的結構
  • 代碼示例
  • 優點和缺點
  • 適用環境
  • 總結

一、什么是裝飾模式

裝飾者模式動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。和代理模式很相似,但在對被裝飾的對象的控制程度是不同的;裝飾者模式是對對象功能的加強,而代理模式是對對象施加控制,并不提供對對象本身功能的加強。

通俗點講,假設現在一杯純豆漿(Soya)賣1元錢,你可以選擇往里邊加糖0.5元(Suger),加蜂蜜0.5元(Honey),加牛奶1元(Milk),加黑豆1元(BlackBean)。這里面純豆漿就是被裝飾者,而糖、蜂蜜、牛奶、黑豆就是裝飾者了。

二、為什么要用該模式

通過繼承的方式可以使子類具有父類的屬性和方法。子類繼承父類后,因為一些業務需求可以通過重寫的方式來加強父類的方法的一些功能,也可以重新定義某些屬性,即覆蓋父類的原有屬性和方法,使其獲得與父類不同的功能。

而裝飾者模式的最基本的功能就是對傳入的一個對象進行功能的加強與優化。

那么問題來了,既然繼承方式也可以對已有類或對象進行加強,那為什么還要衍生出裝飾者模式這一思想呢?

裝飾者模式的意圖定義為:動態地給一個對象添加一些額外的職責。裝飾者模式存在的更重要的意義就在于動態的為對象添加一些功能(或分配額外職責)。

以豆漿的例子為例,先嘗試用繼承的方式分析一下:設豆漿類為基類,每個類中有一個money屬性,那么豆漿加牛奶可模擬為Soya類繼承Milk并重寫pay()方法,如此繼承確實可以計算出每種組合的價錢,于是有下圖:

一般的,我們為了擴展一個類經常使用繼承方式實現,由于繼承為類引入靜態特征,并且隨著擴展功能的增多,子類會很膨脹。

在不想增加很多子類的情況下擴展類,如何實現呢?這時就要用到今天的主角:裝飾者模式了。

Java中的IO機制就用到了裝飾者模式。比如最常用的語句:

BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filepath)));

BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

這就是最常見的裝飾者模式了,通過BufferedReader對已有對象FileReader的功能進行加強和優化。其實它不僅可以加強FileReader,所有的字符輸入流都可以通過這種方式進行包裝。

它是如何實現的呢?

將所有的字符輸入流抽象出了一個基類或接口即Reader,然后通過構造方法的形式將Reader傳遞給BufferedReader,此時BufferedReader就可以對所有的字符輸入流進行攔截和優化了。

如果采用繼承機制,每個XXXReader就要衍生出一個BufferedXXXReader,再加上字符輸出流和字節輸入輸出流,那么Java的IO體系結構該是多么的臃腫不堪啊!而裝飾者模式的出現解決了這個問題,并且,裝飾者的出現也再一次的證明了面向對象的設計原則:多用組合,少用繼承!對擴展開放,對修改關閉

三、模式的結構

  1. Component,給出一個抽象接口或者抽象類,規范實現類的一些方法;
  2. ConcreteComponent:具體的一個組件,實現了抽象方法;
  3. Decorator:抽象的裝飾者,對抽象接口或者抽象類的一個引用,在 method 方法里面使用這個引用完成任務;(代理模式需要實例化)
  4. ConcreteDecorator:具體的裝飾者,對抽象裝飾者的抽象部分進行實現。

第一步:抽象組件

/**
 * 抽象組件
 *
 * 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!");
    }
}

第五步:客戶端運行

/**
 * 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();
    }
}

結果:
Relax, first having a game before work!
work
Relax, first having a game after work!

四、代碼示例

前面其實可以看作是一個代碼示例,下面接著用豆漿的例子來段代碼(這里使用抽象接口):

首先抽象出一個接口,作為裝飾者構造函數的參數,即被裝飾者的父類:

/**
 * Created by w1992wishes on 2017/10/30.
 */
public interface Drink {
    public float money();//獲取價格。
    public String description();//返回商品信息。
}

接下來就是裝飾者類,繼承此接口并通過構造方法獲取被裝飾對象公共接口:

/**
 * Created by w1992wishes on 2017/10/30.
 */
public abstract class Decorator implements Drink{
    private Drink drink;
    public Decorator (Drink drink){
        this.drink = drink;
    }
    @Override
    public String description() {
        return drink.description();
    }
    @Override
    public float money() {
        return drink.money();
    }
}

下面是被裝飾者Soya:

/**
 * 具體的裝飾者對象,純豆漿
 *
 * Created by w1992wishes on 2017/10/30.
 */
public class Soya implements Drink {
    public String description() {
        return "純豆漿";
    }
    public float money() {
        return 2f;
    }
}

下面分別是裝飾者Suger,Milk,BlackBean,Honey類:
Suger

/**
 * 具體的裝飾者類:糖
 *
 * Created by w1992wishes on 2017/10/30.
 */
public class Suger extends Decorator {
    public Suger(Drink drink) {
        super(drink);
    }
    public String description() {
        return super.description()+"+糖";
    }
    public float money() {
        return super.money()+1.5f;
    }
}

Milk

/**
 * 具體的裝飾者對象:牛奶
 *
 * Created by w1992wishes on 2017/10/30.
 */
public class Milk extends Decorator {
    public Milk(Drink drink) {
        super(drink);
    }
    public String description() {
        return super.description()+"+牛奶";
    }
    public float money() {
        return super.money()+1.5f;
    }
}

Honey

/**
 * 具體的裝飾者類:蜂蜜
 * 
 * Created by w1992wishes on 2017/10/30.
 */
public class Honey extends Decorator {
    public Honey(Drink drink) {
        super(drink);
    }
    public String description() {
        return super.description()+"+蜂蜜";
    }
    public float money() {
        return super.money()+1.5f;
    }
}

BlackBean

/**
 * 具體的裝飾者對象:黑豆
 *
 * Created by w1992wishes on 2017/10/30.
 */
public class BlackBean extends Decorator {
    public BlackBean(Drink drink) {
        super(drink);
    }
    public String description() {
        return super.description()+"+黑豆";
    }
    public float money() {
        return super.money()+0.5f;
    }
}

最后是測試代碼:

/**
 * Created by w1992wishes on 2017/10/30.
 */
public class Client {
    public static void main(String[] args) {
        //定義一杯純豆漿
        Soya soya = new Soya();
        System.out.print("-----------------");
        System.out.println(soya.description()+" 價錢:"+soya.money());

        //豆漿加奶
        Milk milkSoya = new Milk(soya);
        System.out.print("-----------------");
        System.out.println(milkSoya.description()+" 價錢:"+milkSoya.money());

        //黑豆豆漿+奶
        BlackBean beanMilkSoya = new BlackBean(milkSoya);
        System.out.print("-----------------");
        System.out.println(beanMilkSoya.description()+"  價錢:"+beanMilkSoya.money());

        //黑豆豆漿+奶+蜂蜜
        Honey honeyBeanMilkSoya = new Honey(beanMilkSoya);
        System.out.print("-----------------");
        System.out.println(honeyBeanMilkSoya.description()+" 價錢:"+honeyBeanMilkSoya.money());
    }
}

結果:
-----------------純豆漿 價錢:2.0
-----------------純豆漿+牛奶 價錢:3.5
-----------------純豆漿+牛奶+黑豆 價錢:4.0
-----------------純豆漿+牛奶+黑豆+蜂蜜 價錢:5.5

五、優點和缺點

5.1、優點:

  • 裝飾模式與繼承關系的目的都是要擴展對象的功能,但是裝飾模式可以提供比繼承更多的靈活性。
  • 可以通過一種動態的方式來擴展一個對象的功能,通過配置文件可以在運行時選擇不同的裝飾器,從而實現不同的行為。
  • 通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創造出很多不同行為的組合。可以使用多個具體裝飾類來裝飾同一對象,得到功能更為強大的對象。
  • 具體構件類與具體裝飾類可以獨立變化,用戶可以根據需要增加新的具體構件類和具體裝飾類,在使用時再對其進行組合,原有代碼無須改變,符合“開閉原則”。

5.2、缺點:

  • 使用裝飾模式進行系統設計時將產生很多小對象,這些對象的區別在于它們之間相互連接的方式有所不同,而不是它們的類或者屬性值有所不同,同時還將產生很多具體裝飾類。這些裝飾類和小對象的產生將增加系統的復雜度,加大學習與理解的難度。
  • 這種比繼承更加靈活機動的特性,也同時意味著裝飾模式比繼承更加易于出錯,排錯也很困難,對于多次裝飾的對象,調試時尋找錯誤可能需要逐級排查,較為煩瑣。

六、適用環境

因為裝飾者模式的以下特點:

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

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

  • 在不影響其他對象的情況下,以動態、透明的方式給單個對象添加職責。
  • 需要動態地給一個對象增加功能,這些功能也可以動態地被撤銷。
  • 當不能采用繼承的方式對系統進行擴充或者采用繼承不利于系統擴展和維護時。不能采用繼承的情況主要有兩類:第一類是系統中存在大量獨立的擴展,為支持每一種組合將產生大量的子類,使得子類數目呈爆炸性增長;第二類是因為類定義不能繼承(如final類)。

七、總結

  • 裝飾模式用于動態地給一個對象增加一些額外的職責,就增加對象功能來說,裝飾模式比生成子類實現更為靈活。它是一種對象結構型模式。
  • 裝飾模式包含四個角色:抽象構件定義了對象的接口,可以給這些對象動態增加職責(方法);具體構件定義了具體的構件對象,實現了在抽象構件中聲明的方法,裝飾器可以給它增加額外的職責(方法);抽象裝飾類是抽象構件類的子類,用于給具體構件增加職責,但是具體職責在其子類中實現;具體裝飾類是抽象裝飾類的子類,負責向構件添加新的職責。
  • 使用裝飾模式來實現擴展比繼承更加靈活,它以對客戶透明的方式動態地給一個對象附加更多的責任。裝飾模式可以在不需要創造更多子類的情況下,將對象的功能加以擴展。
  • 裝飾模式的主要優點在于可以提供比繼承更多的靈活性,可以通過一種動態的 方式來擴展一個對象的功能,并通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創造出很多不同行為的組合,而且具體構件類與具體裝飾類可以獨立變化,用戶可以根據需要增加新的具體構件類和具體裝飾類;其主要缺點在于使用裝飾模式進行系統設計時將產生很多小對象,而且裝飾模式比繼承更加易于出錯,排錯也很困難,對于多次裝飾的對象,調試時尋找錯誤可能需要逐級排查,較為煩瑣。
  • 裝飾模式適用情況包括:在不影響其他對象的情況下,以動態、透明的方式給 單個對象添加職責;需要動態地給一個對象增加功能,這些功能也可以動態地被撤銷;當不能采用繼承的方式對系統進行擴充或者采用繼承不利于系統擴展和維護時。
  • 裝飾模式可分為透明裝飾模式和半透明裝飾模式:在透明裝飾模式中,要求客 戶端完全針對抽象編程,裝飾模式的透明性要求客戶端程序不應該聲明具體構件類型和具體裝飾類型,而應該全部聲明為抽象構件類型;半透明裝飾模式允許用戶在客戶端聲明具體裝飾者類型的對象,調用在具體裝飾者中新增的方法。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,791評論 6 545
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,795評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,943評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,057評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,773評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,106評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,082評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,282評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,793評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,507評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,741評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,220評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,929評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,325評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,661評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,482評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,702評論 2 380

推薦閱讀更多精彩內容

  • 設計模式匯總 一、基礎知識 1. 設計模式概述 定義:設計模式(Design Pattern)是一套被反復使用、多...
    MinoyJet閱讀 3,961評論 1 15
  • 定義 裝飾者模式:在不改變原類文件以及不使用繼承的情況下,動態地將責任附加到對象上,從而實現動態拓展一個對象的功能...
    丶藍天白云夢閱讀 1,661評論 2 23
  • 不使用繼承 動態擴展 不改變原有的類 裝飾器模式是一種結構型模式,它動態的給一個對象添加一些額外的職責。就增加功能...
    spike15閱讀 1,977評論 0 0
  • 裝飾者模式又叫包裝模式,他能夠以透明的方式擴展對象的功能,是繼承方式的另外一種替代方案。和代理模式很相似,但在對被...
    breezedancer閱讀 1,300評論 4 50
  • 本篇文章介紹一種設計模式——裝飾者模式。裝飾者模式在Java中的典型應用就是IO流,在本篇文章中將有詳細介紹。本篇...
    Ruheng閱讀 22,262評論 13 56