歡迎大家提出意見,一起討論!
轉載請標明是引用於 http://blog.csdn.net/chenyujing1234
例子代碼:(編譯工具:Eclipse)
http://www.rayfile.com/zh-cn/files/1291b5bd-9418-11e1-b6a1-0015c55db73d/
參考書籍: <<軟體秘笈-----設計模式那點事>>
裝飾者模式(Decorator ['dekəreitə] Pattren),是在不改變原類檔案和使用繼承的情況下,動態地擴充一個對象的功能,它是通過建立一個封裝對象,也就是
裝飾來包裹真實的對象。
使用裝飾者模式的時候需要注意以下幾點內容: (1) 裝飾對象和真實對象有相同的介面。這樣用戶端對象就可以以和真實對象相同的方式和裝飾對象互動。 (2) 裝飾對象包含一個真實對象的引用。 (3) 裝飾對象接受所有來自用戶端的請求,並把這些請求轉寄給真實的對象。 (4) 裝飾對象可以在轉寄這些請求以前或以後增加一些附加功能。 這樣就確保了在運行時,不用修改給定的對象的結構就可以在外部增加附加功能。 在物件導向的設計中,通常是通過繼承來實現對給定的類的功能擴充,然而,裝飾者模式不需要子類,可以在應用程式運行時動態擴充功能。 1、一般化分析 分析一下染色饅頭的製作過程: (1)需要生產一個正常饅頭; (2)為了節省成本(不使用玉米面),使用染色劑加入到正常饅頭中; (3)通過和面機攪拌,最後生產出“玉米饅頭”。
/** * 饅頭加工介面 * * @author * */public interface IBread {// 準備材料public void prepair();// 和面public void kneadFlour();// 蒸饅頭public void steamed();/** * 加工饅頭方法 */public void process();}
/** * 正常饅頭的實現 * * @author * */public class NormalBread implements IBread {// 準備材料public void prepair() {System.out.println("準備麵粉、水以及發酵粉...");}// 和面public void kneadFlour() {System.out.println("和面...");}// 蒸饅頭public void steamed() {System.out.println("蒸饅頭...香噴噴的饅頭出爐了。");}/** * 加工饅頭方法 */public void process() {// 準備材料prepair();// 和面kneadFlour();// 蒸饅頭steamed();}}
/** * 染色的玉米饅頭 * * @author * */public class CornBread extends NormalBread {// 黑心商販 開始染色了public void paint() {System.out.println("添加檸檬黃的著色劑...");}// 重載父類的和面方法@Overridepublic void kneadFlour() {// 在麵粉中加入 染色劑 之後才開始和面this.paint();// 和面super.kneadFlour();}}
/** * 甜蜜素饅頭 * * @author * */public class SweetBread extends NormalBread {// 黑心商販 開始添加甜蜜素public void paint() {System.out.println("添加甜蜜素...");}// 重載父類的和面方法@Overridepublic void kneadFlour() {// 在麵粉中加入 甜蜜素 之後才開始和面this.paint();// 和面super.kneadFlour();}}
我們知道,饅頭的種類是很多的,現在黑心商販又想生產“甜玉米饅頭"了,怎麼辦叱。你可能要說“使用繼承”不就行了嗎。 再建立一個饅頭,繼承正常饅頭類。不可否認,這樣做是不錯的,可以實現要求。然而,反映到類圖上是又多了一個子類,如果還有其他的饅頭種類, 都要繼承嗎。那樣的話類就要爆炸了,龐大的繼承類別關係圖。 繼承方式存在這樣兩點不利因素: (1)父類的依賴程式過高,父類修改會影響到子類的行為。 (2)不能複用已有的類,造成子類過多。
2、下面我們使用裝飾者模式重新實現染色饅頭的執行個體。 使用裝飾者的靜態類圖,結構如圖所示。 (1)為了裝飾正常饅頭NormalBread,我們需要一個正常饅頭一樣的抽象裝飾者:AbstractBread, 該類和正常饅頭類NormalBread一樣實現IBread饅頭介面,不同的是該抽象類別含有一個IBread介面類型的私人屬性bread, 然後通過構造方法,將外部IBread介面類型對象傳入。
/** * 抽象裝飾者 * * @author * */public abstract class AbstractBread implements IBread {// 儲存傳入的IBread對象private final IBread bread;public AbstractBread(IBread bread) {this.bread = bread;}// 準備材料public void prepair() {this.bread.prepair();}// 和面public void kneadFlour() {this.bread.kneadFlour();}// 蒸饅頭public void steamed() {this.bread.steamed();}// 加工饅頭方法public void process() {prepair();kneadFlour();steamed();}} AbstractBread類滿足了裝飾者的要求: 和真實對象具有相同的介面;包含一個真實對象的引用;接受所有來自用戶端的請求,並反這些請求轉寄給真實的對象; 可以增加一些附加功能。 (2)建立裝飾者。 A、建立染色劑裝飾者----CornDecorator. 建立染色裝飾者"CornDecorator",繼承AbstractBread, 含有修改行為:添加檸檬黃的著色劑。
/** * 染色的玉米饅頭 * * @author * */public class CornDecorator extends AbstractBread {// 構造方法public CornDecorator(IBread bread) {super(bread);}// 黑心商販 開始染色了public void paint() {System.out.println("添加檸檬黃的著色劑...");}// 重載父類的和面方法@Overridepublic void kneadFlour() {// 在麵粉中加入 染色劑 之後才開始和面this.paint();// 和面super.kneadFlour();}}
這和上面提到的CornBread類的內容是一樣的,只是多了一個構造方法。 B、建立甜蜜互裝飾者-----SweetDecorator
/** * 甜蜜素饅頭 * * @author * */public class SweetDecorator extends AbstractBread {// 構造方法public SweetDecorator(IBread bread) {super(bread);}// 黑心商販 開始添加甜蜜素public void paint() {System.out.println("添加甜蜜素...");}// 重載父類的和面方法@Overridepublic void kneadFlour() {// 在麵粉中加入 甜蜜素 之後才開始和面this.paint();// 和面super.kneadFlour();}}
C、生產甜玉米饅頭。 首先建立一個正常饅頭,然後使用甜蜜素裝飾饅頭,之後再用檸檬黃的著色劑裝飾饅頭,最後加工饅頭。
/** * 用戶端應用程式 * * @author * */public class Client {/** * @param args */public static void main(String[] args) {// 生產裝飾饅頭System.out.println("\n====開始裝飾饅頭。。。");// 建立普通的正常饅頭執行個體// 這是我們需要封裝(裝飾)的對象執行個體IBread normalBread = new NormalBread();// 下面就開始 對正常饅頭進行裝飾了。。。// 使用甜蜜素裝飾饅頭normalBread = new SweetDecorator(normalBread);// 使用檸檬黃的著色劑裝飾饅頭normalBread = new CornDecorator(normalBread);// 生產饅頭資訊normalBread.process();System.out.println("====裝飾饅頭結束。。。");}} 運行結果:
====開始裝飾饅頭。。。準備麵粉、水以及發酵粉...添加檸檬黃的著色劑...添加甜蜜素...和面...蒸饅頭...香噴噴的饅頭出爐了。====裝飾饅頭結束。。。
可能你會感覺比較奇怪,我們先使用的是甜蜜素,然後使用的是著色劑,應該先列印甜蜜素,然後再列印著色劑才對。 不用奇怪,這也不矛盾,因為裝飾者相當於對原有對象的封裝,這就像一個禮品盒,最裡面是一個最普通的紙盒, 然後用一般的紙封裝起來,最後使用比較漂亮的封裝紙封裝, 我們最先看到的是最外漂亮的封裝紙,其次才是裡面的一般封裝紙。
3、設計原則
(1)封裝變化部分
設計模式是封裝變化的最好闡釋,無論哪一種設計模式針對的都是軟體中存在的“變化”部分,然後
用抽象對這些“變化”的部分進行封裝。使用抽象的好處在於為軟體的擴充提供了很大的方便性。
在裝飾者模式中合理地利用了類繼承和組合的方式,非常靈活地表達了對象之間的依賴關係。
裝飾者模式應用中“變化”的部分是組件的擴充功能,裝飾者和被裝飾者完全隔離開來,這樣我們就可以任意地改變裝飾者和被裝飾者。 (2)"開-閉"原則
我們在需要對組件進行擴充、增添新的功能行為時,只需要實現一個特定的裝飾者即可,這完全是增量修改,
對原有軟體功能結構沒有影響,對用戶端APP來說也是完全透明的,不必關心內部實現細節。 (3)面向抽象編程,不要面向實現編程
在裝飾者模式中,裝飾者角色就是抽象類別實現,面向抽象編程的好處就在於起到了很好的介面隔離作用。在運用時,
我們具體操作的也是抽象類別引用,這些顯示了面向抽象編程。 (4)優先使用對象組合,而非類繼承。
裝飾者模式最成功在於合理地使用了對象組合方式,通過組合靈活地擴充了組件的功能,所有的擴充功能都是通過組合而非繼承獲得的,
這從根本上決定了是高內聚、低耦合的。 4、使用場合 (1)當我們需要為某個現有的對象動態地增加一個新的功能或職責時,可以考慮使用裝飾者模式;
(2)當某個對象的職責經常發生變化或者經常需要動態地增加職責,避免為了適應這樣的變化而增加繼承子類擴充的方式,因為這種方式會造成子類膨脹的速度過快,難以控制,此時可以使用裝飾者模式。
用戶端不會覺得對象在裝飾前和裝飾後有什麼不同,裝飾者模式可以在不建立更多子類的情況下,將對象的功能加以擴充,裝飾者模式使用原來被裝飾的一個子類執行個體,
把用戶端的調用委派到裝飾者。
下面我們來看一下裝飾者模式的靜態類圖.使我們對裝飾者模式有一個更加清晰的認識
A、被裝飾者抽象Component:是一個介面或抽象類別,是定義的核心對象。
在裝飾者模式中,必然有一個被撮出來最核心、最原始的介面。這個類就是我們需要裝飾類的基類。本例中是IBread介面。
B、被裝飾者具體實現ConcreteComponent:這是Component類的一個實作類別,我們要裝飾的就是這個具體的實作類別。本例中是NormalBread
C、裝飾者Decorator:一般是一個抽象類別。它裡面有一個指向Component變數的引用。
D、裝飾者實現ConcreteDecorator1和ConcreteDecorator2.