設計模式之裝飾者模式

概述
裝飾者模式是在不必改變原類文件和使用繼承的情況下,動態地擴展一個對象的功能。它是通過創建一個包裝對象,也就是裝飾來包裹真實的對象。


特點
(1).裝飾者和被裝飾者對象有相同的超類型。
(2).你可以用一個或多個裝飾者包裝一個對象。
(3).既然裝飾者和被裝飾者對象有相同的超類型,所以在任何需要原始對象(被包裝的)的場合,可以用裝飾多的對象代替它。
(4).裝飾者可以在所委託被裝飾者的行爲之前與/或之後,加上自己的行爲,以達到特定的目的。
(5).對象可以在任何時候被裝飾,所以可以在運行時動態的、不限量的用你喜歡的裝飾者來裝飾對象。


設計原則
1. 多用組合,少用繼承。
利用繼承設計子類的行爲,是在編譯時靜態決定的,而且所有的子類都會繼承到相同的行爲。然而,如果能夠利用組合的做法擴展對象的行爲,就可以在運行時動態地進行擴展。
2.類應該對擴展開放,對修改關閉。


類圖



代碼示例
案例來自Head First,星巴茲咖啡的訂單系統問題:購買咖啡時,可以要求在其中加入各種調料,例如豆漿、摩卡等,星巴茲會根據所加入的調料收取不同的費用。

先從Beverage類下手,如下所示:

// Beverage是一個抽象類,有兩個方法:getDescription()和cost()。
public abstract class Beverage {
    String description = "Unknow Beverage";

    // getDescription()已經實現了
    public String getDescription() {
        return description;
    }

    // cost()必須在子類中實現
    public abstract double cost();
}


Beverage類很簡單。讓我們來實現Condiment(調料)抽象類,也就是裝飾者類吧:
// 首先,必須讓CondimentDecorator能夠取代Beverage,所以將CondimentDecorator擴展自Beverage類。
public abstract class CondimentDecorator extends Beverage {
    // 所有的調料裝飾者都必須重新實現getDescription()方法。
    public abstract String getDescription();
}

飲料的代碼
現在,已經有了基類,我們開始實現一些飲料。先從濃縮咖啡(Espresso)開始。別忘了,我們需要爲具體的飲料設置描述,而且還必須實現cost()方法。

// 首先,讓Espresso擴展自Beverage類,因爲Espresso是一種飲料。
public class Espresso extends Beverage {
    // 爲了要設置飲料的描述,我們寫了一個構造器。記住,description實例變量繼承自Beverage類。
    public Espresso() {
        description = "Espresso";
    }

    // 最後,需要計算Espresso的價錢,現在不需要管調料的價錢,直接把Espresso的價格1.99返回即可。
    public double cost() {
        return 1.99;
    }
}

// 這是另一種飲料,做法和Espresso一樣,只是把Espresso名稱改爲HouseBlend,並返回價錢0.89。
public class HouseBlend extends Beverage {
    public Espresso() {
        description = "Espresso";
    }

    // 最後,需要計算Espresso的價錢,現在不需要管調料的價錢,直接把Espresso的價格1.99返回即可。
    public double cost() {
        return 0.89;
    }
}

// 你可以自行建立另外的飲料類,做法都一樣。

調料的代碼
如果回頭去看看裝飾者模式的類圖,將發現我們已經完成了抽象組件(Beverage),有了具體的組件(HouseBlend),也有了抽象裝飾者(CondimentDecorator)。現在,我們就來實現具體的裝飾者。先從摩卡下手:

// Mocha是一個裝飾者,所以讓它擴展自CondimentDecorator。別忘了,CondimentDecorator擴展自Beverage。
public class Mocha extends CondimentDecorator {
    Beverage beverage;

    // 要讓Mocha能夠引用一個Beverage,做法如下:
    // (1)用一個實例變量記錄飲料,也就是被裝飾者。
    // (2)想辦法讓被裝飾者(飲料)被記錄到實例變量中。這裏的做法是,把飲料當做構造器的參數,再由構造器將此飲料記錄在實例變量中。
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    // 我們希望description不只是描述飲料(例如“DarkRoast”),而是完整地連調料都描述出來(例如“DarkRoast, Mocha”)。
    // 所以首先利用委託的做法,得到一個描述,然後在其後加上附加的描述(例如“Mocha”)。
    public String getDescription() {
        return beverage.getDescription() + ", Mocha";
    }

    // 要計算貸Mocha飲料的價錢,首先把調用委託給被裝飾對象,以計算價錢,然後再加上Mocha的價錢,得到最後結果。
    public double cost() {
        return 0.20 + beverage.cost();
    }
}

下訂單的測試代碼
public class StarbuzzCoffee {
    public static void main(String args[]) {
        // 訂一杯Espresso,不需要調料,打印出它的描述與價錢。
        Beverage beverage = new Espresso();
	System.out.println(beverage.getDescription + " $" + beverage.cost());

	// 製造出一個DarkRoast對象。
        Beverage beverage2 = new DarkRoast();
	// 用Mocha裝飾它
	beverage2 = new Mocha(beverage2);
	// 用第二個Mocha裝飾它
	beverage2 = new Mocha(beverage2);
	// 用Whip裝飾它
	beverage2 = new Whip(beverage2);
	System.out.println(beverage2.getDescription + " $" + beverage2.cost());

	// 最後,再來一杯調料爲豆漿,摩卡,奶泡的HouseBlend咖啡。
        Beverage beverage3 = new HouseBlend();
	beverage3 = new Soy(beverage3);
	beverage3 = new Mocha(beverage3);
	beverage3 = new Whip(beverage3);
	System.out.println(beverage3.getDescription + " $" + beverage3.cost());
    }
}

典型案例
java IO相關的類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章