軟體設計模式之(二)裝飾者模式

來源:互聯網
上載者:User

歡迎大家提出意見,一起討論!

 

轉載請標明是引用於 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.

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.