裝飾模式(Decorator)
裝飾模式又名包裝(Wrapper)模式。
裝飾模式以對客戶端透明的方式擴展對象的功能,是繼承關系的一個替代方案。
裝飾模式通過創建一個包裝對象,也就是裝飾,來包裹真實的對象。
裝飾模式以對客戶端透明的方式動態地給一個對象附加上更多的責任。換言之,客戶端並不會覺得對象在裝飾前和裝飾后有什么不同。
裝飾模式可以在不創造更多子類的情況下,將對象的功能加以擴展。
裝飾模式把客戶端的調用委派到被裝飾類。裝飾模式的關鍵在於這種擴展是完全透明的。
裝飾模式的角色
抽象構件角色(Component):給出一個抽象接口,以規范准備接收附加責任的對象。
具體構件角色(Concrete Component):定義將要接收附加責任的類。
裝飾角色(Decorator):持有一個構件(Component)對象的引用,並定義一個與抽象構件接口一致的接口。
具體裝飾角色(Concrete Decorator):負責給構件對象“貼上”附加的責任。
Java IO中的裝飾模式
在IO中,具體構件角色是節點流,裝飾角色是過濾流。
FilterInputStream和FilterOutputStream是裝飾角色,而其他派生自它們的類則是具體裝飾角色。
裝飾模式的特點
裝飾對象和真實對象有相同的接口。這樣客戶端對象就可以以和真實對象相同的方式和裝飾對象交互。
裝飾對象包含一個真實對象的引用(reference)。
裝飾對象接收所有來自客戶端的請求,它把這些請求轉發給真實的對象。
裝飾對象可以在轉發這些請求之前或之后附加一些功能。
這樣就確保了在運行時,不用修改給定對象的結構就可以在外部增加附加的功能。
程序實例
public void doSomething();
}
這是抽象構件角色,是一個接口。具體構件角色實現這個接口:
public class ConcreteComponent implements Component{ @Override public void doSomething() { System.out.println("功能A"); } }
裝飾角色:
public class Decorator implements Component{ private Component component; public Decorator(Component component) { this.component = component; } @Override public void doSomething() { component.doSomething(); } }
具體裝飾角色(兩個):
public class ConcreteDecorator1 extends Decorator{ public ConcreteDecorator1(Component component) { super(component); } @Override public void doSomething() { super.doSomething(); this.doAnotherThing(); } private void doAnotherThing() { System.out.println("功能B"); } } public class ConcreteDecorator2 extends Decorator{ public ConcreteDecorator2(Component component) { super(component); } @Override public void doSomething() { super.doSomething(); this.doAnotherThing(); } private void doAnotherThing() { System.out.println("功能C"); } }
使用測試:
public class Client{ public static void main(String[] args) { Component component = new ConcreteComponent(); Component component1 = new ConcreteDecorator1(component); component1.doSomething(); System.out.println("-----------"); Component component2 = new ConcreteDecorator2(component1); component2.doSomething(); } }
問題引入
咖啡店的類設計:
一個飲料基類,各種飲料類繼承這個基類,並且計算各自的價錢。
飲料中需要加入各種調料,考慮在基類中加入一些布爾值變量代表是否加入各種調料,基類的cost()中的計算各種調料的價錢,子類覆蓋cost(),並且在其中調用超類的cost(),加上特定飲料的價錢,計算出子類特定飲料的價錢。
缺點:類數量爆炸、基類加入的新功能並不適用於所有的子類、調料價錢的改變、新調料的出現都會要求改變現有代碼;有的子類並不適合某些調料等情況……
設計原則
類應該對擴展開放,對修改關閉。
我們的目標是允許類容易擴展,在不修改現有代碼的情況下,就可搭配新的行為。
如能實現這樣的目標,有什么好處呢?這樣的設計具有彈性可以應對改變,可以接受新的功能來應對改變的需求。
要讓OO設計同時具備開放性和關閉性,不是一件容易的事,通常來說,沒有必要把設計的每個部分都這么設計。
遵循開放-關閉原則,通常會引入新的抽象層次,增加代碼的復雜度。
我們需要把注意力集中在設計中最有可能改變的地方,然后應用開放-關閉原則。
用裝飾者模式解決問題
解決咖啡店飲料問題的方法:
以飲料為主體,然后在運行時以調料來“裝飾”飲料。
比如,顧客想要摩卡(Mocha)和奶泡(Whip)深焙咖啡(DarkRoast):
DarkRoast繼承自Beverage,有一個cost()方法。
第一步,以DarkRoast對象開始;
第二步,顧客想要摩卡,所以建立一個Mocha裝飾者對象,並用它將DarkRoast對象包裝(wrap)起來;
第三步,顧客想要奶泡,所以建立一個Whip裝飾者對象,並用它將Mocha對象包起來;(Mocha和Whip也繼承自Beverage,有一個cost()方法);
最后,為顧客算錢,通過調用最外圈裝飾者(Whip)的cost()就可以。Whip()的cost()會先委托它裝飾的對象(Mocha)計算出價錢,然后在加上奶泡的價錢。Mocha的cost()也是類似。
裝飾者模式的特點
裝飾者和被裝飾對象有相同的超類型。
可以用一個或多個裝飾者包裝一個對象。
因為裝飾者和被裝飾者具有相同的類型,所以任何需要原始對象的場合,可以用裝飾過的對象代替。
裝飾者可以在所委托被裝飾者的行為之前與/或之后,加上自己的行為,以達到特定的目的。
對象可以在任何時候被裝飾,所以可以在運行時動態地、不限量地用你喜歡的裝飾者來裝飾對象。
裝飾者模式的定義
裝飾者模式動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。
裝飾者模式的實現
實現類圖如下:
裝飾者和被裝飾者具有共同的超類,利用繼承達到“類型匹配”,而不是利用繼承獲得“行為”;將裝飾者和被裝飾者組合時,加入新的行為。
解決本文中飲料的具體問題時,圖中Component即為Beverage(可以是抽象類或者接口),而ConcreteComponent為各種飲 料,Decorator(抽象裝飾者)為調料的抽象類或接口,ConcreteDecoratorX則為各種具體的調料。
因為使用對象組合,可以把飲料和調料更有彈性地加以混合與匹配。
代碼外部細節:
代碼中實現的時候,通過構造函數將被裝飾者傳入裝飾者中即可,如最后的調用形式如下:
Beverage beverage = new DarkRoast(); beverage = new Mocha(beverage); beverage = new Whip(beverage);
即完成了兩層包裝,此時再調用beverage的cost()函數即可得到總價。
java.io包內的裝飾者模式
裝飾者模式的缺點:在設計中加入大量的小類,如果過度使用,會讓程序變得復雜。


