1. 程式人生 > >Head First 設計模式總結(三) 裝飾者模式

Head First 設計模式總結(三) 裝飾者模式

本文對裝飾者模式進行了概括和總結

裝飾者模式

動態的將責任附加到物件上。若要擴充套件功能,裝飾者提供了比繼承更有彈性的替代方案。 一旦你熟悉了裝飾的技巧,你將能夠在不修改任何程式碼的前提下,給你的(或別人的)物件賦予新的職責。

問題描述

有一家名為“星巴茲咖啡”的咖啡店,隨著飲品種類的增加,他們的訂單系統需要更新。現在有四種咖啡種類:HouseBlend、DarkRoast、Decaf、Espresso,還有四種調料:Milk、Soy、Mocha、Whip,每種咖啡都可以搭配這四種調料中的一種或者多種。

有一個濫用繼承的人這樣做:他將所有情況都用一個具體的類弄出來,並讓每一個類都去繼承Beverage(飲料)這個頂層類。這樣的話,就算每種咖啡只搭配一種調料的話,就已經有16種咖啡品種了,這相當的複雜。當其中一種調料(比如牛奶)的價格變化時,就要去所有用到了牛奶的飲品類中去做相應修改,維護成本特別高。

現在用裝飾者模式解決這個問題

做法:

以飲料為主體,在執行時用調料去裝飾飲料,比方說,顧客需要摩卡和奶泡深焙咖啡,需要做的是: 1、弄一個深焙咖啡物件(DarkRoast) 2、用摩卡(Mocha)物件裝飾它 3、用奶泡(Whip)物件裝飾它 4、呼叫cost()方法和getDescription()方法,通過委託將調料的價格全部累加到飲料的價格上,得到總價和最後的飲料名稱。 下圖是採用裝飾者模式應該用的框架: 在這裡插入圖片描述

抽象類Beverage為頂層類,CondimentDecorator類為裝飾者類的頂層類,所有裝飾者都繼承於它,為了裝飾Beverage,抽象類CondimentDecorator也必須繼承Beverage。

下面是實現程式碼: 先給出Beverage類的程式碼

public abstract class Beverage {
    String description;
    public String getDescription(){
        return description;
    }
    public abstract double cost();
}

其中一種咖啡DarkRoast的實現如下

public class DarkRoast extends Beverage {
    DarkRoast(){
        description = "DarkRoast coffee";
    }
    @Override
    public double cost() {
        return 0.99;   //價格為0.99
    }
}

再給出CondimentDecorator類的程式碼

/*
*由於CondimentDecorator類繼承了Beverage,只需加上額外的方法或者成員
*/
public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();  //加入調料的話,咖啡的名稱會被調料修飾
}

下面給出兩種調料(Mocha和Whip)的程式碼 Mocha的程式碼

public class Mocha extends CondimentDecorator {
    Beverage beverage;  //利用組合將待裝飾的Beverage物件進行裝飾
    Mocha(Beverage beverage){
        this.beverage = beverage;   //將待裝飾的beverage物件通過構造器傳進來,以對其資訊進行裝飾
    }
    @Override
    public double cost() {
        return beverage.cost() + 0.20;//經過Mocha裝飾後,飲料的總價在原來的基礎上又增加了0.2
    }
    @Override
    public String getDescription() {
        return beverage.getDescription() + " + Mocha"; //經過Mocha裝飾後,飲料的描述也加上了Mocha
    }
}

Whip的程式碼

public class Whip extends CondimentDecorator {
    Beverage beverage;

    Whip(Beverage beverage){
        this.beverage = beverage;
    }
    @Override
    public double cost() {
        return beverage.cost() + 0.10;//Whip額外加收0.1
    }
    @Override
    public String getDescription() {
        return beverage.getDescription() + " + Whip";
    }
}

現在已經有了一種咖啡和兩種調料,可以先測試一下裝飾者

用咖啡店的名字作為測試用的主類,測試程式碼如下:

public class StarBuzzCoffee {
    public static void main(String [] args){
        Beverage beverage = new DarkRoast();//只有咖啡的飲料
        System.out.println("當前飲料為:"+beverage.getDescription()+"  價格為:"+beverage.cost());
        beverage = new Mocha(beverage);//在咖啡的基礎上加入Mocha
        System.out.println("當前飲料為:"+beverage.getDescription()+"  價格為:"+beverage.cost());
        beverage = new Whip(beverage);//在之前的基礎上加上Whip
        System.out.println("當前飲料為:"+beverage.getDescription()+"  價格為:"+beverage.cost());
    }
}

得到的結果為:

當前飲料為:DarkRoast coffee  價格為:0.99
當前飲料為:DarkRoast coffee + Mocha  價格為:1.19
當前飲料為:DarkRoast coffee + Mocha + Whip  價格為:1.29

[注]

上述的beverage = new Mocha(beverage)之所以成立,是因為Mocha等調料也繼承自Beverage,這個語句相當於進行了向下轉型,Beverage是頂層元件,下面的子類都是用來裝飾它的,儘管執行這句話之後beverage引用的物件型別就變成了Mocha,但是Mocha繼承自Beverage,這個beverage還是Beverage,不用去在意這些細節,因為“beverage”這個名字一開始就給他了,不管今後有多少個裝飾者裝飾它,它永遠叫"beverage",並不是很影響可讀性。

總結

這節提出了一個設計原則(開閉原則):對擴充套件開放,對修改關閉。 當軟體需要變化時,儘量通過擴充套件軟體實體的行為來實現變化,而不是通過修改已有的程式碼來實現變化。 用抽象構建框架,用實現擴充套件細節。因為抽象靈活性好,適應性廣,只要抽象的合理,可以基本保持軟體架構的穩定。而軟體中易變的細節,我們用從抽象派生的實現類來進行擴充套件,當軟體需要發生變化時,我們只需要根據需求重新派生一個實現類來擴充套件就可以了。當然前提是我們的抽象要合理,要對需求的變更有前瞻性和預見性才行。

裝飾者也會使設計中出現許多小物件,不能過度使用,否則會讓程式變得非常複雜。