裝飾模式(俄羅斯套娃?)
裝飾模式:動態的給某些對象添加額外的功能
參考:
簡書 | 裝飾模式
博客園 | 簡說設計模式——裝飾模式
博客園 | 裝飾器模式 Decorator 結構型 設計模式 (十)
什么是裝飾模式
裝飾模式也叫裝飾器模式,python中的裝飾器就是這種模式的體現,對于一個類,如果要添加一個新功能,除了修改代碼外(違反開閉原則),可以使用繼承,但通過繼承添加新功能并不適合所有場景,如
- 類不可見或不允許繼承
- 需要對一批類似的兄弟類添加同一個新功能時,繼承會產生大量的子類
- 希望新功能的添加和撤銷是動態的
- …
裝飾模式中的對象包括:
-
裝飾器(用來為被裝飾對象動態添加新功能)
-
抽象被裝飾對象(所有能被裝飾對象的抽象)
-
被裝飾對象
客戶端如果希望給某個對象動態添加一個新功能,就可以把這個對象(被裝飾對象)傳遞給裝飾器,由裝飾器實現新功能,并保存一個被裝飾對象的引用,并返回給客戶端一個裝飾器對象,這樣,被裝飾對象原來的行為和屬性并沒有改變,甚至被裝飾對象本身就沒有改變,只是在外面套了一個殼子,新功能是這個殼子提供的。就像TCP/IP協議棧中,應用層的數據包到傳輸層通過加TCP或UDP首部來傳輸一樣。
裝飾模式優缺點
優點:
- 一個裝飾器可以給多個不同的類動態添加新功能
- 新功能由裝飾器實現,不需要修改被裝飾對象,有一定的安全性
- 多個裝飾器可以配合嵌套使用,以此實現更復雜的功能
- 新功能不影響原來的功能,添加和撤銷都方便
缺點:
-
過多的裝飾類可能使程序變得很復雜
-
裝飾模式是針對抽象組件(Component)類型編程。但是,如果你要針對具體組件編程時,就應該重新思考你的應用架構,以及裝飾者是否合適。當然也可以改變Component接口,增加新的公開的行為,實現“半透明”的裝飾者模式。在實際項目中要做出最佳選擇。
作者:慵懶的陽光丶
適用場景
- 要添加的新功能與原有類關聯不大時
- 新功能需要方便添加和撤銷時
- 不能或不方便通過繼承實現新功能時
例
比如賣烤冷面,最基本的就是面(抽象被裝飾對象)具體的就是烤冷面(被裝飾對象),然后可以往面里面加各種配料(抽象裝飾器),如雞蛋,辣條等(具體裝飾器),由于不同配料的加入順序對最后的烤冷面有影響,所以如果要用繼承拓展“烤冷面”,那先加雞蛋再加辣條和先加辣條再加雞蛋就需要寫兩個子類,造成冗余重復,這種場景就適合適用裝飾模式。
抽象被裝飾對象
package pers.junebao.decorator_pattern;public abstract class Noodles {public String rawMaterial; // 配料public abstract void sayWhoAmI();
}
具體的被裝飾對象:
package pers.junebao.decorator_pattern;public class BakedColdNoodles extends Noodles {BakedColdNoodles() {this.rawMaterial = "面"; // 最原始的烤冷面,配料只有面}@Overridepublic void sayWhoAmI() {System.out.println("我是普通烤冷面!");}
}
抽象裝飾器:
package pers.junebao.decorator_pattern.decorator;import pers.junebao.decorator_pattern.Noodles;public abstract class Burden extends Noodles {public Noodles noodles; // 裝飾器中保留一份被裝飾對象的引用,方便客戶端使用public Burden(Noodles noodles) {this.noodles = noodles;}
}
- 裝飾器是為某一類對象提供裝飾的(這里就是實現了Noodles 的類)
具體的裝飾器類:
-
加雞蛋
package pers.junebao.decorator_pattern.decorator;import pers.junebao.decorator_pattern.Noodles;public class AddEggs extends Burden {public AddEggs(Noodles noodles) {super(noodles);this.rawMaterial = noodles.rawMaterial + ", 雞蛋";}@Overridepublic void sayWhoAmI() {System.out.println("我是加了雞蛋的烤冷面!!");}}
-
加辣條
package pers.junebao.decorator_pattern.decorator;import pers.junebao.decorator_pattern.Noodles;public class AddSpicyStrips extends Burden{public AddSpicyStrips(Noodles noodles) {super(noodles);this.rawMaterial = noodles.rawMaterial + " ,辣條";}@Overridepublic void sayWhoAmI() {System.out.println("我是加了辣條的烤冷面!!");} }
客戶端:
package pers.junebao.decorator_pattern;import pers.junebao.decorator_pattern.decorator.AddEggs;
import pers.junebao.decorator_pattern.decorator.AddSpicyStrips;public class Main {public static void main(String[] args) {Noodles bcn = new BakedColdNoodles();Noodles bcnAddEgg = new AddEggs(bcn);bcnAddEgg.sayWhoAmI();System.out.println(bcnAddEgg.rawMaterial);Noodles bcnEggSpicyS = new AddSpicyStrips(bcnAddEgg);bcnEggSpicyS.sayWhoAmI();System.out.println(bcnEggSpicyS.rawMaterial);}
}
/*
我是加了雞蛋的烤冷面!!
面, 雞蛋
我是加了辣條的烤冷面!!
面, 雞蛋 ,辣條*/
這樣如果想先加辣條在家雞蛋,就可以使用AddSpicyStrips先裝飾BakedColdNoodles,再用AddEggs裝飾AddSpicyStrips。
GitHub | 完整代碼