設計模式12:裝飾者模式
裝飾者模式
裝飾器模式是一種用於代替繼承的技術,無需通過繼承增加子類就能擴充套件物件的新功能。使用物件的關聯關係代替繼承關係,更加靈活,同時避免型別體系的快速膨脹。
怎麼說?老樣子,先設想個場景
假如我要去樓下買手抓餅吃,一般來說,手抓餅都會提供不同的配料,比如加雞蛋,加雞柳,加肉鬆或者加火腿,不同的配料價錢也不同
是不是感覺餓啦哈哈哈哈,我們繼續...
如果我們要用程式語言來描述的話,我們首先想到的就是先定義一個基類,然後每增加一種搭配就寫個子類
定義一個手抓餅基類,啥都沒加的那種
public class HandPanCake { String name; public HandPanCake(String name) { this.name = name; } public void eatSense(){ System.out.println("啥都沒加,難吃的一匹"); }
如果我們要增加配料,比如要加個蛋的話,我們繼承手抓餅類進行拓展
class HandPanCakeWithEgg extends HandPanCake{
public HandPanCakeWithEgg(String name) {
super(name);
}
//增加配料
public void addEgg(){
System.out.println("加了雞蛋,美滋滋");
}
public void eatSense(){
super.eatSense();
addEgg();
}
}
同樣,我們也可以類似分別寫出增加 雞柳、肉鬆、火腿等的搭配。到目前為止的話,都沒什麼問題
但是,如果某天我撿到錢了,於是買手抓餅的時候同時打算增加雞蛋和火腿這兩樣的組合(就是又加雞蛋又加火腿啦),那麼現在要怎麼操作呢?是不是同樣用繼承的方式呢? 如果我又打算加雞蛋和肉鬆,是不是又要寫個子類呢?再換一種包含多個配料的組合的搭配,則又要再寫個子類,這樣的話,如果我們的搭配非常多,則需要建立非常多的子類,顯然這不是我們想要看到的結果,所以我們就提出了新的思路,而這種思路,就是我們要講的裝飾者模式
再小小的總結下,裝飾者模式解決的是組合搭配搭配問題,當獨立搭配(單獨加雞柳、單獨加雞蛋,單獨加..)數量增加時,其組合搭配數量是爆炸式增長的...
主要角色
- Component抽象構件角色。真實物件和裝飾物件有相同的介面。這樣,客戶端物件就能夠以與真實物件相同的方式同裝飾物件互動。
- ConcreteComponent 具體構件角色(真實物件):
- Decorator裝飾角色:持有一個抽象構件的引用。裝飾物件接受所有客戶端的請求,並把這些請求轉發給真實的物件。這樣,就能在真實物件呼叫前後增加新的功能。
- ConcreteDecorator具體裝飾角色:負責給構件物件增加新的責任。
例項講解
首先定義一個抽象的手抓餅介面,裡面定義了吃的感覺這一方法
public interface IhandPanCake {
void eatSense();
}
接著是實現手抓餅介面,構建真實物件(是一個啥都沒加的裸餅)
//真實角色,實現抽象介面
public class HandPanCake implements IhandPanCake{
public void eatSense(){
System.out.println("啥都沒加,難吃的一匹");
}
}
再也就是裝飾者角色,同樣也是實現手抓餅介面,該角色持有一個抽象構建的引用,負責把請求轉發給真實角色
public class HandPanCakeDecorator implements IhandPanCake{
protected IhandPanCake handPanCake;
public HandPanCakeDecorator(IhandPanCake handPanCake) {
super();
this.handPanCake = handPanCake;
}
@Override
public void eatSense() {
handPanCake.eatSense();
}
}
接下來就是定義具體的裝飾角色了,分別負責把雞蛋、火腿這些增加到具體的手抓餅上
加蛋
public class HandPanCakeWithEgg extends HandPanCakeDecorator{
public HandPanCakeWithEgg(IhandPanCake handPanCake) {
super(handPanCake);
}
public void addEgg(){
System.out.println("加了雞蛋,美滋滋");
}
public void eatSense() {
super.eatSense();
addEgg();
}
}
加雞柳
public class HandPanCakeWithChicken extends HandPanCakeDecorator{
public HandPanCakeWithChicken(IhandPanCake handPanCake) {
super(handPanCake);
}
public void addChicken(){
System.out.println("加了雞柳,吃了流口水");
}
public void eatSense() {
super.eatSense();
addChicken();
}
}
加火腿
public class HandPanCakeWithHam extends HandPanCakeDecorator{
public HandPanCakeWithHam(IhandPanCake handPanCake) {
super(handPanCake);
}
public void addHam(){
System.out.println("加了火腿,有點爽");
}
public void eatSense() {
super.eatSense();
addHam();
}
}
客戶端測試
採用裝飾者模式的話,當我們要增加多種組合的搭配時,就不需要增加新的子類了,而是
HandPanCake handPanCake = new HandPanCake();
handPanCake.eatSense();
System.out.println("老闆,加個雞柳和蛋..................");
HandPanCakeWithChicken handPanCakeWithChickenAndEgg = new HandPanCakeWithChicken(handPanCakeWithEgg);
handPanCakeWithChickenAndEgg.eatSense();
當然也可以用set方法來組裝裝飾類
優點
- 擴充套件物件功能,比繼承靈活,不會導致類個數急劇增加
- 可以對一個物件進行多次裝飾,創造出不同行為的組合,得到功能更加強大的物件具體構建類和具體裝飾類可以獨立變化
- 使用者可以根據需要自己增加新的具體構件子類和具體裝飾子。
缺點
- 產生很多小物件。大量小物件佔據記憶體,一定程度上影響效能。
- 裝飾模式易於出錯,除錯排查比較麻煩。
實際開發中的應用
- IO中輸入流和輸出流的設計(最典型)
- Servlet API 中提供了一個request物件的Decorator設計模式的預設實現類HttpServletRequestWrapper,HttpServletRequestWrapper 類,增強了request物件的功能。
橋接模式與裝飾模式
這兩個模式似乎有點像?兩個模式都是為了解決子類過多問題, 但他們的誘因不同:
1.橋接模式物件自身有 沿著多個維度變化的趨勢 , 本身不穩定;
2.裝飾者模式物件自身非常穩定, 只是為了增加新功能/增強原功能。