裝飾者模式

裝飾模式是一種用于替代繼承的技術(shù),它通過一種無須定義子類的方式來給對象動態(tài)增加職責(zé),使用對象之間的關(guān)聯(lián)關(guān)系取代類之間的繼承關(guān)系。

概覽

定義

Decorator Pattern: Attaches additional responsibility to anobject dynamically. Decorators provide a flexible alternative to subclassingfor extending functionality.

裝飾模式(DecoratorPattern)在不改變原類文件以及不使用繼承的情況下,動態(tài)地將責(zé)任附加到對象上,從而實現(xiàn)動態(tài)拓展一個對象的功能。

UML類圖

參與者

參與者

Component(抽象構(gòu)件)

  • 是一個接口或者抽象類,用來定義基本行為
  • 是具體構(gòu)件和抽象裝飾類的共同父類,聲明了在具體構(gòu)件中實現(xiàn)的業(yè)務(wù)方法,它的引入可以使客戶端以一致的方式處理未被裝飾的對象以及裝飾之后的對象,實現(xiàn)客戶端的透明操作。

ConcreteComponent(具體構(gòu)件)

  • 抽象構(gòu)件類的子類,用于定義具體的構(gòu)件對象(即被裝飾者)
  • 實現(xiàn)了在抽象構(gòu)件中聲明的方法,裝飾器可以給它增加額外的職責(zé)(方法)

Decorator(抽象裝飾類)

  • 一個接口或抽象類,也是抽象構(gòu)件類的子類,用于給具體構(gòu)件增加職責(zé),但是具體職責(zé)在其子類中實現(xiàn)
  • 對于ConcreteComponent來說,不需要知道Decorator的存在,
  • 維護(hù)一個指向抽象構(gòu)件對象的引用,通過該引用可以調(diào)用裝飾之前構(gòu)件對象的方法,并通過其子類擴(kuò)展該方法,以達(dá)到裝飾的目的

ConcreteDecorator(具體裝飾類)

  • 抽象裝飾類的子類,負(fù)責(zé)向構(gòu)件添加新的職責(zé)
  • 每一個具體裝飾類都定義了一些新的行為,它可以調(diào)用在抽象裝飾類中定義的方法,并可以增加新的方法用以擴(kuò)充對象的行為。
  • 每個裝飾者都應(yīng)該有一個實例變量用以保存某個Component的引用,持有Component的引用后,由于其自身也是Component的子類,相當(dāng)于ConcreteDecorator包裹了Component,不但有Component的特性,同時自身也可以有別的特性,也就是所謂的裝飾。

設(shè)計的原則

裝飾者和被裝飾者有相同超類

這里利用繼承是為了達(dá)到類型匹配,而不是利用繼承獲得行為
因為裝飾者和被裝飾者是同一個類型,因此裝飾者可以取代被裝飾者,這樣就使被裝飾者擁有了裝飾者獨有的行為。

用組合而不是繼承

利用繼承設(shè)計子類,只能在編譯時靜態(tài)決定,并且所有子類都會繼承相同的行為;利用組合的做法擴(kuò)展對象,就可以在運(yùn)行時動態(tài)的進(jìn)行擴(kuò)展。
利用裝飾者,我們可以實現(xiàn)新的裝飾者增加新的行為,而不用修改現(xiàn)有代碼,而如果單純依賴?yán)^承,每當(dāng)需要新行為時,還得修改現(xiàn)有的代碼。遵循了開放-關(guān)閉原則:類應(yīng)該對擴(kuò)展開放,對修改關(guān)閉。

調(diào)用方式

  1. 包裹
    它是通過創(chuàng)建一個包裝對象,也就是裝飾來包裹真實的對象
  2. 保存被裝飾者的引用
    在裝飾模式中引入了裝飾類,在裝飾類中既可以調(diào)用待裝飾的原有類的方法,還可以增加新的方法,以擴(kuò)充原有類的功能(在裝飾者類中調(diào)用被裝飾者類的方法,封裝成新的功能方法)
  3. 裝飾者類擁有被裝飾者類的對象,一般是當(dāng)構(gòu)造參數(shù)傳入

一個例子

分析

一個小攤賣手抓餅和烤冷面(都是小吃)。
點了手抓餅和烤冷面之后還可以在這個基礎(chǔ)之上增加一些配料,例如煎蛋,火腿片等,每個配料的價格都不一樣。
隨意搭配配料,最終價格是手抓餅和烤冷面基礎(chǔ)價+每一種所選配料價格的總和。小攤的價格單如下:

小吃(Snack) 價格
手抓餅(Handcake) 5
烤冷面(Noodles) 7
配料(Condiment ) 價格
煎蛋(Friedegg) 1
火腿(Ham) 2
培根(Bacon) 3

UML類圖

用裝飾者模式處理,主體是被裝飾者,而配料則是裝飾者,UML類圖:

UML類圖

代碼實現(xiàn):

結(jié)構(gòu)

Snack

public abstract class Snack {
    public String desc = "我不是一個具體的食物";
    public String getDesc() {
        return desc;
    }
    public abstract double cost();
}

Handcake

public class Handcake extends Snack {
    public Handcake() {
        desc = "手抓餅";
    }
    @Override
    public double cost() {
        return 5;
    }
}

Noodles

public class Noodles extends Snack {
    public Noodles() {
        desc = "烤冷面";
    }
    @Override
    public double cost() {
        return 7;
    }
}

Condiment

public abstract class Condiment extends Snack {
    public abstract String getDesc();
}

FiredEgg

public class FiredEgg extends Condiment {
    private Snack snack;// 持有Component的引用
    public FiredEgg(Snack snack) {
        this.snack = snack;
    }
    @Override
    public String getDesc() {
        return snack.getDesc() + ", 煎蛋";
    }
    @Override
    public double cost() {
        return snack.cost() + 1;
    }
}

Ham

public class Ham extends Condiment {
    private Snack snack;// 持有Component的引用
    public Ham(Snack snack) {
        this.snack = snack;
    }
    @Override
    public String getDesc() {
        return snack.getDesc() + ", 火腿";
    }
    @Override
    public double cost() {
        return snack.cost() + 2;
    }
}

Bacon

public class Bacon extends Condiment {
    private Snack snack;// 持有Component的引用
    public Bacon(Snack snack) {
        this.snack = snack;
    }
    @Override
    public String getDesc() {
        return snack.getDesc() + ", 培根";
    }
    @Override
    public double cost() {
        return snack.cost() + 3;
    }
}

測試類:TestDecorate

public class TestDecorate {
    public static void main(String[] args) {
        Snack snack1 = new Handcake();
        System.out.println(snack1.getDesc() + "純手抓餅花費(fèi)" + snack1.cost());
        snack1 = new FiredEgg(snack1);
        System.out.println(snack1.getDesc() + "手抓餅+煎蛋總共花費(fèi)" + snack1.cost());

        Snack snack2 = new Noodles();
        System.out.println(snack2.getDesc() + "純烤冷面花費(fèi)" + snack2.cost());
        snack2 = new FiredEgg(snack2);
        snack2 = new Ham(snack2);
        snack2 = new Bacon(snack2);
        System.out.println(snack2.getDesc() + "烤冷面+煎蛋+火腿+培根總共花費(fèi)" + snack2.cost());
    }
}

輸出

輸出

小結(jié)分析

Noodles、FiredEgg、Ham、Bacon都是繼承自Snack 基類,但是具體實現(xiàn)不同:

  • Noodles是Snack的直接子類,是被裝飾者
  • FiredEgg、Ham、Bacon是裝飾者,保存了Snack的引用,實現(xiàn)了cost()方法,并且在cost()方法內(nèi)部,不但實現(xiàn)了自己的邏輯,同時也調(diào)用了Snack引用的cost()方法,即獲取了被裝飾者的信息,這是裝飾者的一個特點,保存引用的目的就是為了獲取被裝飾者的狀態(tài)信息,以便將自身的特性加以組合。

總結(jié)

1、裝飾者和被裝飾者對象有相同的超類型,所以在任何需要原始對象(被裝飾者)的場合,都可以用裝飾過得對象代替原始對象
2、可以用一個或多個裝飾者包裝一個對象(被裝飾者)
3、裝飾者可以在所委托的裝飾者行為之前或之后加上自己的行為,以達(dá)到特定的目的
4、被裝飾者可以在任何時候被裝飾,所以可以在運(yùn)行時動態(tài)地、不限量地用你喜歡的裝飾者來裝飾對象
5、裝飾者會導(dǎo)致出現(xiàn)很多小對象,如果過度使用,會讓程序變得復(fù)雜

Java I/O是裝飾者模式的典型應(yīng)用

參考文章
Java設(shè)計模式之裝飾者模式(Decorator pattern)
學(xué)習(xí)、探究Java設(shè)計模式——裝飾者模式
設(shè)計模式學(xué)習(xí)筆記之三:裝飾者模式
設(shè)計模式(結(jié)構(gòu)型)之裝飾者模式(Decorator Pattern)

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

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

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