設計模式之裝飾者模式(Decorator Pattern)

裝飾者模式可以做到在不修改任何底層代碼的情況下,給對象增加的新的方法。
首先,我們通過對一個現實問題的模擬分析,了解什么是裝飾者模式以及裝飾者模式的作用。


問題提出

咖啡店在街頭隨處可見。我們以咖啡店的飲品訂單系統為例。假設我們要設計一個飲品的訂單系統。
設計了一個這樣的類圖:

Paste_Image.png

Beverage是一個 抽象類,所有咖啡店的飲品都必須繼承這個類,description是飲品的描述信息,cost()是計算此種飲品的價格。

我們會遇到這樣的問題,在購買飲品的時候,我們可以要求在其中加入不同的調料配品,比如,摩卡(Mocha),加奶泡等。除了原本飲料需要的價格的外,咖啡店會根據所加入調料的再收取不同的費用。

如果按照之前的設計方式,那么會出現如下的情況:

Paste_Image.png

** 顯然這似乎已經是類爆炸了!**

而且我們永遠無法預測,顧客會選取怎樣的調料的搭配,每當出現一個新的調料搭配時,我們就需要增加一個新的類。
更加糟糕的是,當原料配料的價格上漲后或者下降后,那么所有涉及到這種配料的類都得重新改過。這簡直是個噩夢!很顯然這很不符合我們設計模式的原則。作為一個程序員,我們是決不能容忍這種情況發生的!

那么我們該如何設計呢?

這里就需要用到我們的裝飾者模式!

引出裝飾者模式

讓我們轉換思路,我們以飲品beverage為主體,在運行時以顧客選擇的調料來裝飾beverage。比如,如果顧客想要摩卡和奶泡的拿鐵咖啡,我們要做的應該是這樣的:

  • 取一個拿鐵咖啡的對象
Paste_Image.png
  • 用摩卡對象裝飾它
Paste_Image.png
  • 用奶泡對象裝飾它
Paste_Image.png
  • 調用cost方法計算價錢,并依賴委托將配料摩卡和奶泡加上去。
Paste_Image.png

會先計算whip的cost然后調用mocha的cost,然后調用拿鐵的cost,這樣就計算出了總價格。
這樣就是實現的裝飾者模式解決這個問題的思路。
下面我們看一下裝飾者模式的定義,以及代碼實現的基本思路

定義裝飾者模式

裝飾者模式動態的將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。

Paste_Image.png

這個類圖就是裝飾者模式的實現方式。更詳細的是如下這個版本的類圖。

Paste_Image.png

下面我們就根據這個類圖來解決我們之前在實現咖啡店飲料系統上遇到的問題。

Paste_Image.png

分析設計類圖:

  • beverage相當于抽象的component類,具體的component和decorator都需要繼承實現這個抽象類。
  • 四個具體的飲料的類,相當于concrete component!每一個類代表了一個飲料類型。
  • condimentDecorator是抽象的decorator類,它是所有調料類的抽象,它保存了beverage的一個引用。
  • 調料裝飾者類繼承自condimentDecorator,是各種具體調料的實現,他們都實現了cost方法。

上面有一個非常關鍵的地方,就是我們注意到裝飾者和被裝飾者必須是一樣的類型,也就是擁有共同的超類。這樣做是因為我們要裝飾者必須能取代被裝飾者。
這樣我們就可以利用對象的組合,將調料和飲料的行為組合起來。這符合我們之前提到的設計原則多用組合,少用繼承

實現裝飾者模式

如果看到這里還是不太清楚,也沒關系,接下來我們將具體實現代碼,對裝飾者模式有一個直觀根本的了解。

  • 首先實現beverage和condiment兩個抽象類
package abstractComponent;

public abstract class Beverage {
    protected String description = "Unknow Beverage";
    
    public String getDescription() {
        return description;
    }
    
    public abstract double cost();
}
package abstractDecrator;
import abstractComponent.Beverage;


public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}
  • 然后我們實現具體的飲料類
package concreteComponent;
import abstractComponent.Beverage;


public class Coco extends Beverage {
    public Coco(){
        description = "Coco";
    }
    
    public double cost(){
        return 0.89;
    }
}
package concreteComponent;
import abstractComponent.Beverage;


public class Espresso extends Beverage {
    public Espresso() {
        description = "Espresso";
    }
    
    public double cost() {
        return 1.99;
    }
}
  • 我們再實現具體的裝飾者類,也就是調料類
package concreteDecorator;

import abstractComponent.Beverage;
import abstractDecrator.CondimentDecorator;

public class Mocha extends CondimentDecorator {
    
    Beverage beverage;
    
    public Mocha(Beverage beverage){
        this.beverage = beverage;
    }
    
    
    @Override
    public double cost() {
        // TODO Auto-generated method stub
        return .20 + beverage.cost();
    }

    @Override
    public String getDescription() {
        // TODO Auto-generated method stub
        return beverage.getDescription() + ", Mocha";
    }

}
package concreteDecorator;

import abstractComponent.Beverage;
import abstractDecrator.CondimentDecorator;

public class Soy extends CondimentDecorator {
    
    Beverage beverage;
    
    public Soy(Beverage beverage){
        this.beverage = beverage;
    }
    
    @Override
    public String getDescription() {
        // TODO Auto-generated method stub
        return beverage.getDescription() + ", Soy";
    }

    @Override
    public double cost() {
        // TODO Auto-generated method stub
        return .15 + beverage.cost();
    }

}
package concreteDecorator;

import abstractComponent.Beverage;
import abstractDecrator.CondimentDecorator;

public class Whip extends CondimentDecorator {
    
    Beverage beverage;
    
    public Whip(Beverage beverage){
        this.beverage = beverage;
    }
    
    @Override
    public String getDescription() {
        // TODO Auto-generated method stub
        return beverage.getDescription() + " , whip";
    }

    @Override
    public double cost() {
        // TODO Auto-generated method stub
        return .10 + beverage.cost();
    }

}
  • 最后編寫一個測試類,來測試我們裝飾者模式的效果如何
import concreteComponent.Coco;
import concreteComponent.Espresso;
import concreteDecorator.Mocha;
import concreteDecorator.Soy;
import concreteDecorator.Whip;
import abstractComponent.Beverage;


public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Beverage beverage = new Espresso();
        System.out.println( beverage.getDescription() + "$" + beverage.cost());
        
        Beverage beverage2 = new Coco();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println( beverage2.getDescription() + "$" + beverage2.cost());
        
        Beverage beverage3 = new Espresso();
        beverage3 = new Whip(beverage3);
        beverage3 = new Mocha(beverage3);
        beverage3 = new Soy(beverage3);
        System.out.println( beverage3.getDescription() + "$" + beverage3.cost());
    }

}

總結與分析

通過裝飾者模式我們可以很好的解決咖啡店的問題,用裝飾者去包裝組件,可以達到很好的可擴展性。

  • 裝飾者模式用到的技術主要有兩種就是組合和委托,這幫助我們動態的在運行時加上新的行為。
  • 裝飾者模式意味著一群裝飾者類,這些類用來包裝裝飾者。
  • 裝飾者和被裝飾者類實際上具有相同類型的。
  • 裝飾者可以在被裝飾者的行為前面或后面加上自己的行為,甚至完全覆蓋。
  • 但裝飾者模式的使用會導致出現很多小對象,就是裝飾者對象,過度使用也會使程序變得復雜。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容