裝飾模式是一種用于替代繼承的技術(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é)任附加到對象上,從而實(shí)現(xiàn)動態(tài)拓展一個對象的功能昼扛。
UML類圖
參與者
Component(抽象構(gòu)件)
- 是一個接口或者抽象類寸齐,用來定義基本行為
- 是具體構(gòu)件和抽象裝飾類的共同父類,聲明了在具體構(gòu)件中實(shí)現(xiàn)的業(yè)務(wù)方法抄谐,它的引入可以使客戶端以一致的方式處理未被裝飾的對象以及裝飾之后的對象渺鹦,實(shí)現(xiàn)客戶端的透明操作。
ConcreteComponent(具體構(gòu)件)
- 抽象構(gòu)件類的子類蛹含,用于定義具體的構(gòu)件對象(即被裝飾者)
- 實(shí)現(xiàn)了在抽象構(gòu)件中聲明的方法毅厚,裝飾器可以給它增加額外的職責(zé)(方法)
Decorator(抽象裝飾類)
- 一個接口或抽象類,也是抽象構(gòu)件類的子類浦箱,用于給具體構(gòu)件增加職責(zé)吸耿,但是具體職責(zé)在其子類中實(shí)現(xiàn)
- 對于ConcreteComponent來說,不需要知道Decorator的存在酷窥,
- 它維護(hù)一個指向抽象構(gòu)件對象的引用咽安,通過該引用可以調(diào)用裝飾之前構(gòu)件對象的方法,并通過其子類擴(kuò)展該方法蓬推,以達(dá)到裝飾的目的
ConcreteDecorator(具體裝飾類)
- 抽象裝飾類的子類妆棒,負(fù)責(zé)向構(gòu)件添加新的職責(zé)
- 每一個具體裝飾類都定義了一些新的行為,它可以調(diào)用在抽象裝飾類中定義的方法沸伏,并可以增加新的方法用以擴(kuò)充對象的行為糕珊。
- 每個裝飾者都應(yīng)該有一個實(shí)例變量用以保存某個Component的引用,持有Component的引用后毅糟,由于其自身也是Component的子類放接,相當(dāng)于ConcreteDecorator包裹了Component,不但有Component的特性留特,同時自身也可以有別的特性纠脾,也就是所謂的裝飾。
設(shè)計的原則
裝飾者和被裝飾者有相同超類
這里利用繼承是為了達(dá)到類型匹配蜕青,而不是利用繼承獲得行為
因?yàn)檠b飾者和被裝飾者是同一個類型,因此裝飾者可以取代被裝飾者,這樣就使被裝飾者擁有了裝飾者獨(dú)有的行為苟蹈。
用組合而不是繼承
利用繼承設(shè)計子類,只能在編譯時靜態(tài)決定右核,并且所有子類都會繼承相同的行為慧脱;利用組合的做法擴(kuò)展對象,就可以在運(yùn)行時動態(tài)的進(jìn)行擴(kuò)展贺喝。
利用裝飾者菱鸥,我們可以實(shí)現(xiàn)新的裝飾者增加新的行為宗兼,而不用修改現(xiàn)有代碼,而如果單純依賴?yán)^承氮采,每當(dāng)需要新行為時殷绍,還得修改現(xiàn)有的代碼。遵循了開放-關(guān)閉原則:類應(yīng)該對擴(kuò)展開放鹊漠,對修改關(guān)閉主到。
調(diào)用方式
- 包裹
它是通過創(chuàng)建一個包裝對象,也就是裝飾來包裹真實(shí)的對象 - 保存被裝飾者的引用
在裝飾模式中引入了裝飾類躯概,在裝飾類中既可以調(diào)用待裝飾的原有類的方法登钥,還可以增加新的方法,以擴(kuò)充原有類的功能(在裝飾者類中調(diào)用被裝飾者類的方法娶靡,封裝成新的功能方法) - 裝飾者類擁有被裝飾者類的對象牧牢,一般是當(dāng)構(gòu)造參數(shù)傳入
一個例子
分析
一個小攤賣手抓餅和烤冷面(都是小吃)。
點(diǎn)了手抓餅和烤冷面之后還可以在這個基礎(chǔ)之上增加一些配料姿锭,例如煎蛋塔鳍,火腿片等,每個配料的價格都不一樣艾凯。
隨意搭配配料献幔,最終價格是手抓餅和烤冷面基礎(chǔ)價+每一種所選配料價格的總和懂傀。小攤的價格單如下:
小吃(Snack) | 價格 |
---|---|
手抓餅(Handcake) | 5 |
烤冷面(Noodles) | 7 |
配料(Condiment ) | 價格 |
---|---|
煎蛋(Friedegg) | 1 |
火腿(Ham) | 2 |
培根(Bacon) | 3 |
UML類圖
用裝飾者模式處理趾诗,主體是被裝飾者,而配料則是裝飾者蹬蚁,UML類圖:
代碼實(shí)現(xiàn):
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 基類贝乎,但是具體實(shí)現(xiàn)不同:
- Noodles是Snack的直接子類,是被裝飾者
- FiredEgg叽粹、Ham览效、Bacon是裝飾者,保存了Snack的引用虫几,實(shí)現(xiàn)了cost()方法锤灿,并且在cost()方法內(nèi)部,不但實(shí)現(xiàn)了自己的邏輯辆脸,同時也調(diào)用了Snack引用的cost()方法但校,即獲取了被裝飾者的信息,這是裝飾者的一個特點(diǎn)啡氢,保存引用的目的就是為了獲取被裝飾者的狀態(tài)信息状囱,以便將自身的特性加以組合术裸。
總結(jié)
1、裝飾者和被裝飾者對象有相同的超類型亭枷,所以在任何需要原始對象(被裝飾者)的場合袭艺,都可以用裝飾過得對象代替原始對象
2、可以用一個或多個裝飾者包裝一個對象(被裝飾者)
3奶栖、裝飾者可以在所委托的裝飾者行為之前或之后加上自己的行為匹表,以達(dá)到特定的目的
4、被裝飾者可以在任何時候被裝飾宣鄙,所以可以在運(yùn)行時動態(tài)地袍镀、不限量地用你喜歡的裝飾者來裝飾對象
5、裝飾者會導(dǎo)致出現(xiàn)很多小對象冻晤,如果過度使用苇羡,會讓程序變得復(fù)雜
參考文章
Java設(shè)計模式之裝飾者模式(Decorator pattern)
學(xué)習(xí)、探究Java設(shè)計模式——裝飾者模式
設(shè)計模式學(xué)習(xí)筆記之三:裝飾者模式
設(shè)計模式(結(jié)構(gòu)型)之裝飾者模式(Decorator Pattern)