### 一、咖啡店的故事
這次我們借用HeadFirst中的咖啡店的故事來討論一下裝飾模式。咖啡店中有各種種類的咖啡和咖啡需要加的配料。有一家咖啡店為了提高效率打算開發一套咖啡訂購系統,用戶可以根據清單選擇咖啡和咖啡所加的配料,系統可以自動的計算總價格。
第一種方案是這個樣子的:

Beverge是一個抽象類,店內所有的飲料都必須繼承自這個類。description用來描述這個是什么類型的飲料例如:Dark Roast。getDescription()方法返回這種飲料的描述,cost()返回這種飲料的描述,但是cost()方法是抽象的,具體的實現由子類決定,因為每中不同飲料價格都不相同。下面的四個類代表四種不同的飲料,他們都實現了父類的cost方法。
因為用戶購買咖啡時還需要搭配一些調料,所以每種咖啡又分為各種不同的種類,于是系統就變成了這個樣子。

這種方案造成的最大的問題就是類的數量眾多,維護成本非常大。試想如果牛奶的價格上漲,那么每種添加牛奶的咖啡就都必須修改自己的價格,所以這種方案不符合“開-閉”原則。
第二種方案:

milk,soy,mocha,whip都是代表有沒有這種調料的布爾值,下面的hasMilk()方法等是返回這個變量的值,setMilk()方法是設置這個變量的值。并且Beverage中的cost()方法也不再是抽象方法而是返回所加的各種調料的價格,子類會覆蓋這個cost方法,子類的cost方法會返回總的價格,首先調用父類的cost方法得到調料的價格,然后再加上咖啡的價格,返回總的價格。
采用這種方法只需要5各類就能表示出這家咖啡店中所有的咖啡和配料組合。但是仍然存在問題,試想,如果我們如果增加了一種新的調料,那么Beverage類不就需要更改嗎,如果客戶想買雙倍摩卡咖啡怎么辦?如果以后開發出了一種新的飲料,比如說“茶”,對于這種飲料而言某些調料是不合適的,比如說(奶泡),但是它還是繼承了所有的方法,包括不合適的。
第三種方案:裝飾模式
我們把咖啡本身當做是主要的本體,而把奶泡等調料當做是咖啡的裝飾,我們通過給本體添加不同的裝飾來獲得不同的結果。
這是咖啡的本體,我們以DarkRoast為例,這種咖啡是繼承自Beverage的,它的cost()是用來返回咖啡的價格。

如果客戶想要摩卡咖啡,就建立一個Mocha對象,并用他將DarkRoast對象包起來(裝飾)。Mocha就是一個裝飾者,他的類型和他所裝飾的類型是一樣的,都是Beverage。Mocha也有一個cost方法,它的cost()方法會首先調用所裝飾的對象DarkRoast類的cost()方法獲得咖啡的價格,然后再加上自己本身的價格形成最后總的價格。

如果顧客也想要加入奶泡,那就在建立一個Whip的裝飾者,并用他將上邊的Mocha對象包起來,Whip也是繼承自Beverage。

現在,當為顧客計算價格的時候,通過最外圈裝飾者(Whip)的cost就可以辦到。Whip的cost()會委托它所裝飾的對象計算出價錢,然后加上奶泡的價錢。

### 二、裝飾模式的定義
裝飾模式(Decorator Pattern) :動態地給一個對象增加一些額外的職責(Responsibility),就增加對象功能來說,裝飾模式比生成子類實現更為靈活。
### 三、裝飾模式的結構

從上圖中可以看出,裝飾模式一共有四部分組成:
1、抽象組件角色(Component):定義一個對象接口,以規范準備接受附加責任的對象,即可以給這些對象動態地添加職責。
2、具體組件角色(ConcreteComponent) :被裝飾者,定義一個將要被裝飾增加功能的類。可以給這個類的對象添加一些職責。
3、抽象裝飾器(Decorator):維持一個指向構件Component對象的實例,并定義一個與抽象組件角色Component接口一致的接口。
4、具體裝飾器角色(ConcreteDecorator):向組件添加職責。
### 四、裝飾模式的實例
下面是上邊所說的咖啡館的代碼。
~~~
package com.designpattern.decorator;
/**
* 抽象組件,裝飾者和被裝飾者都繼承自它
* @author 98583
*
*/
public abstract class Beverage {
/**
* 飲料的名稱,用來代表是哪種飲料
*/
protected String description = "Unknown Beverage";
public String getDescription() {
return description;
}
/**
* 每個子類都有自己的實現方法
* @return
*/
public abstract double cost();
}
~~~
~~~
package com.designpattern.decorator;
/**
* 抽象裝飾者,定義具體裝飾者要實現的代碼
* @author 98583
*
*/
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
~~~
~~~
package com.designpattern.decorator;
/**
* 具體的裝飾者
* @author 98583
*
*/
public class Mocha extends CondimentDecorator {
/**
* 保留一個被裝飾者的引用
*/
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
public double cost() {
return .20 + beverage.cost();
}
}
~~~
~~~
package com.designpattern.decorator;
/**
* 被裝飾者
* @author 98583
*
*/
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "DarkRoast";
}
public double cost() {
return .99;
}
}
~~~
~~~
package com.designpattern.decorator;
public class StarbuzzCoffee {
public static void main(String[] args) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + " $" + beverage.cost());
Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out
.println(beverage2.getDescription() + " $" + beverage2.cost());
Beverage beverage3 = new HouseBlend();
beverage3 = new Soy(beverage2);
beverage3 = new Mocha(beverage2);
beverage3 = new Whip(beverage2);
System.out
.println(beverage3.getDescription() + " $" + beverage3.cost());
}
}
~~~
其他類的代碼都是相似的,這里不再貼出,最后會附上源碼。
### 五、裝飾模式的優缺點
**裝飾模式的優點:**
- 裝飾模式與繼承關系的目的都是要擴展對象的功能,但是裝飾模式可以提供比繼承更多的靈活性。
- 可以通過一種動態的方式來擴展一個對象的功能,通過配置文件可以在運行時選擇不同的裝飾器,從而實現不同的行為。
- 通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創造出很多不同行為的組合。可以使用多個具體裝飾類來裝飾同一對象,得到功能更為強大的對象。
- 具體構件類與具體裝飾類可以獨立變化,用戶可以根據需要增加新的具體構件類和具體裝飾類,在使用時再對其進行組合,原有代碼無須改變,符合“開閉原則”
**裝飾模式的缺點:**
- 使用裝飾模式進行系統設計時將產生很多小對象,這些對象的區別在于它們之間相互連接的方式有所不同,而不是它們的類或者屬性值有所不同,同時還將產生很多具體裝飾類。這些裝飾類和小對象的產生將增加系統的復雜度,加大學習與理解的難度。
- 這種比繼承更加靈活機動的特性,也同時意味著裝飾模式比繼承更加易于出錯,排錯也很困難,對于多次裝飾的對象,調試時尋找錯誤可能需要逐級排查,較為煩瑣。
### 六、裝飾模式的適用環境
1、在不影響其他對象的情況下,以動態、透明的方式給單個對象添加職責。
2、需要動態地給一個對象增加功能,這些功能也可以動態地被撤銷。
3、當不能采用繼承的方式對系統進行擴充或者采用繼承不利于系統擴展和維護時。不能采用繼承的情況主要有兩類:第一類是系統中存在大量獨立的擴展,為支持每一種組合將產生大量的子類,使得子類數目呈爆炸性增長;第二類是因為類定義不能繼承(如final類).
源碼下載:[http://download.csdn.net/detail/xingjiarong/9317741](http://download.csdn.net/detail/xingjiarong/9317741)
- 前言
- 設計原則(一)"開-閉"原則(OCP)
- 設計原則(二)里氏替換原則(LSP)
- 設計原則(三)組合復用原則
- 設計原則(四)依賴倒置原則(DIP)
- 設計模式(一)簡單工廠模式
- 設計模式(二)工廠方法模式
- 設計模式(三)抽象工廠模式
- 設計模式(四)單例模式
- 設計模式(五)創建者模式(Builder)
- 設計模式(六)原型模式
- 設計模式(七)門面模式(Facade Pattern 外觀模式)
- 設計模式(八)橋梁模式(Bridge)
- 設計模式(九)裝飾模式(Decorator)
- 設計模式(十)適配器模式
- 設計模式(十一)策略模式
- 設計模式(十二)責任鏈模式
- 設計模式之UML(一)類圖以及類間關系(泛化 、實現、依賴、關聯、聚合、組合)
- 設計模式之橋梁模式和策略模式的區別