Decorator
裝飾者模式:(裝飾者)動態的將責任附加到對象(被裝飾者)上去,若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。
模式中的角色
- 抽象構件(Component)角色:給出一個抽象接口,以規範準備接收附加責任的對象。
- 具體構件(ConcreteComponent)角色:定義一個將要接收附加責任的類。
- 裝飾(Decorator)角色:持有一個構件(Component)對象的實例,並定義一個與抽象構件接口一致的接口。
具體裝飾(ConcreteDecorator)角色:負責給構件對象“貼上”附加的責任。
裝飾模式類圖如下
特點
- 裝飾者和被裝飾者有相同的超類型
- 對象可以在任何時候被裝飾,所以可以在運行時動態地,不限量地用你喜歡的裝飾者來裝飾。
示例
星巴克是以擴張速度最快而聞名的咖啡連鎖店。如果你在結交看到它的店,在對面街上肯定也會另一家。因爲擴展速度實在太快了,他們準備更新訂單系統,以合乎他們的飲料供應要求。
他們原先的類設計是這樣……
1. Beverage(飲料)是所有具體飲料(HouseBlend:黑咖啡,DarkRoast:深度烘焙咖啡,Decaf:脫咖啡因咖啡,Espresso:意式濃縮咖啡)的抽象,定義一個具體方法getDescription,和抽象的cost方法,計算咖啡價錢,此方法需要具體類實現。
2. HouseBlend、Decaf、DarkRoast和Espresso繼承自Beverage。
現在問題來了,在購買咖啡的時候可以加入各種作料,例如蒸奶、豆漿、摩卡或者奶泡。星巴克會根據多加入的作料收取不同的費用,所以訂單系統必須考慮到這些作料的價錢。下面是他們進行的第一次修改嘗試。
每個類都實現不同的cost方法。很明顯,星巴克爲自己創造了一個維護噩夢,若果牛奶的價錢上揚,怎麼辦?是不是需要修改多有涉及到牛奶類的源代碼。新增一種焦糖調料風味時,怎麼辦?是不是需要設計一些添加焦糖的咖啡類。
下面是他們進行的第二次修改嘗試。
笨透了,幹嘛設計這麼多類呀?利用實例變量和繼承,就可以追蹤這些調料呀?
重新設計了Beverage類,加上實例變量代表是否加上了作料,提供了cost方法的實現,計算加入各種作料咖啡的價錢,cost的實現如下
Beverage子類同過 set作料 加入不同的作料,通過調用父類的cost方法完成價錢的計算。
這下子類的數目少了很多,可是還有很多問題沒有解決:
- 調料的價錢改變了該怎麼辦
- 出現新的作料該怎麼辦
- 萬一顧客想加入兩份摩卡該怎麼辦
下面是他們進行的第三次修改嘗試:以裝飾者構造飲料訂單
代碼示例
Beverage類
public abstract class Beverage{
String description = "Unknown Beverage";
public String getDescrption(){
return descrition;
}
public abstract double cost();
}
具體的飲料代碼
public class Expresso extends Beverage{
public Espresso(){
description = "Espresso";
}
public double cost(){
return 1.99;
}
}
public class HouseBlend extends Beverage{
public HouseBlend (){
description = "HouseBlend ";
}
public double cost(){
return .89;
}
}
Condiment作料類抽象類,也即是裝飾者類
public abstract class CondimentDecorator extends Beverage{
public abstract String getDescrption();
}
具體的調料類代碼
public class Mocha extends CondimentDecorator{
Beverage beverage;
public Mocha(Beverage beverage){
this.beverage = beverage;
}
public String getDescrption(){
return beverage.getDescrption() + ",Mocha";
}
public double cost(){
return .20 + beverage.cost();
}
}
測試代碼
public class StarbuzzCoffee{
//加雙份mocha的咖啡
Beverage beverage = new Espresso();
beverage = new Mocha (beverage);
beverage = new Mocha (beverage);
}
半透明的裝飾模式
裝飾模式有透明和半透明兩種,這兩種的區別就在於裝飾角色的接口與抽象構件角色的接口是否完全一致。透明的裝飾模式也就是理想的裝飾模式,要求具體構件角色、裝飾角色的接口與抽象構件角色的接口完全一致。相反,如果裝飾角色的接口與抽象構件角色接口不一致,也就是說裝飾角色的接口比抽象構件角色的接口寬的話,這種裝飾模式也是可以接受的,稱爲“半透明”的裝飾模式。
半透明的裝飾模式舉例
InputStream類型中的裝飾模式是半透明的。爲了說明這一點,不妨看一看作裝飾模式的抽象構件角色的InputStream的源代碼。這個抽象類聲明瞭九個方法,並給出了其中八個的實現,另外一個是抽象方法,需要由子類實現。
public abstract class InputStream implements Closeable {
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException {}
public int read(byte b[], int off, int len) throws IOException {}
public long skip(long n) throws IOException {}
public int available() throws IOException {}
public void close() throws IOException {}
public synchronized void mark(int readlimit) {}
public synchronized void reset() throws IOException {}
public boolean markSupported() {}
}
下面是作爲裝飾模式的抽象裝飾角色FilterInputStream類的源代碼。可以看出,FilterInputStream的接口與InputStream的接口是完全一致的。也就是說,直到這一步,還是與裝飾模式相符合的。
public class FilterInputStream extends InputStream {
protected FilterInputStream(InputStream in) {}
public int read() throws IOException {}
public int read(byte b[]) throws IOException {}
public int read(byte b[], int off, int len) throws IOException {}
public long skip(long n) throws IOException {}
public int available() throws IOException {}
public void close() throws IOException {}
public synchronized void mark(int readlimit) {}
public synchronized void reset() throws IOException {}
public boolean markSupported() {}
}
下面是具體裝飾角色PushbackInputStream的源代碼
public class PushbackInputStream extends FilterInputStream {
private void ensureOpen() throws IOException {}
public PushbackInputStream(InputStream in, int size) {}
public PushbackInputStream(InputStream in) {}
public int read() throws IOException {}
public int read(byte[] b, int off, int len) throws IOException {}
public void unread(int b) throws IOException {}
public void unread(byte[] b, int off, int len) throws IOException {}
public void unread(byte[] b) throws IOException {}
public int available() throws IOException {}
public long skip(long n) throws IOException {}
public boolean markSupported() {}
public synchronized void mark(int readlimit) {}
public synchronized void reset() throws IOException {}
public synchronized void close() throws IOException {}
}
查看源碼,你會發現,這個裝飾類提供了額外的方法unread(),這就意味着PushbackInputStream是一個半透明的裝飾類。換言之,它破壞了理想的裝飾模式的要求。如果客戶端持有一個類型爲InputStream對象的引用in的話,那麼如果in的真實類型是 PushbackInputStream的話,只要客戶端不需要使用unread()方法,那麼客戶端一般沒有問題。但是如果客戶端必須使用這個方法,就必須進行向下類型轉換。將in的類型轉換成爲PushbackInputStream之後纔可能調用這個方法。但是,這個類型轉換意味着客戶端必須知道它 拿到的引用是指向一個類型爲PushbackInputStream的對象。這就破壞了使用裝飾模式的原始用意。
現實世界與理論總歸是有一段差距的。純粹的裝飾模式在真實的系統中很難找到。一般所遇到的,都是這種半透明的裝飾模式