擴充套件系統功能——裝飾模式(四)

Liuwei-Sunny發表於2012-04-04

12.4 透明裝飾模式與半透明裝飾模式

      裝飾模式雖好,但存在一個問題。如果客戶端希望單獨呼叫具體裝飾類新增的方法,而不想通過抽象構件中宣告的方法來呼叫新增方法時將遇到一些麻煩,我們通過一個例項來對這種情況加以說明:

Sunny軟體公司開發的Sunny OA系統中,採購單(PurchaseRequest)和請假條(LeaveRequest)等檔案(Document)物件都具有顯示功能,現在要為其增加審批、刪除等功能,使用裝飾模式進行設計。

      我們使用裝飾模式可以得到如圖12-5所示結構圖:

12-5檔案物件功能增加例項結構圖

      在圖12-5中,Document充當抽象構件類,PurchaseRequestLeaveRequest充當具體構件類,Decorator充當抽象裝飾類,ApproverDeleter充當具體裝飾類。其中Decorator類和Approver類的示例程式碼如下所示:

//抽象裝飾類

class Decorator implements  Document

{

      private Document  document;

      

      public Decorator(Document  document)

      {

             this.  document = document;

      }

      

      public void display()

      {

             document.display();

      }

}

 

//具體裝飾類

class Approver extends  Decorator

{

      public Approver(Document document)

      {

             super(document);

             System.out.println("增加審批功能!");

      }

      

      public void approve()

      {

             System.out.println("審批檔案!");

      }

}

      大家注意,Approver類繼承了抽象裝飾類Decoratordisplay()方法,同時新增了業務方法approve(),但這兩個方法是獨立的,沒有任何呼叫關係。如果客戶端需要分別呼叫這兩個方法,程式碼片段如下所示:

Document  doc; //使用抽象構件型別定義

doc = new PurchaseRequest();

Approver newDoc; //使用具體裝飾型別定義

newDoc = new Approver(doc);

newDoc.display();//呼叫原有業務方法

newDoc.approve();//呼叫新增業務方法

      如果newDoc也使用Document型別來定義,將導致客戶端無法呼叫新增業務方法approve(),因為在抽象構件類Document中沒有對approve()方法的宣告。也就是說,在客戶端無法統一對待裝飾之前的具體構件物件和裝飾之後的構件物件。

      在實際使用過程中,由於新增行為可能需要單獨呼叫,因此這種形式的裝飾模式也經常出現,這種裝飾模式被稱為半透明(Semi-transparent)裝飾模式,而標準的裝飾模式是透明(Transparent)裝飾模式。下面我們對這兩種裝飾模式進行較為詳細的介紹:

(1)透明裝飾模式

      在透明裝飾模式中,要求客戶端完全針對抽象程式設計,裝飾模式的透明性要求客戶端程式不應該將物件宣告為具體構件型別或具體裝飾型別,而應該全部宣告為抽象構件型別。對於客戶端而言,具體構件物件和具體裝飾物件沒有任何區別。也就是應該使用如下程式碼:

Component  c, c1; //使用抽象構件型別定義物件

c = new ConcreteComponent()

c1 = new ConcreteDecorator (c)

      而不應該使用如下程式碼:

ConcreteComponent c; //使用具體構件型別定義物件

c = new ConcreteComponent()

      或

ConcreteDecorator c1; //使用具體裝飾型別定義物件

c1 = new ConcreteDecorator(c)

      在12.3節圖形介面構件庫的設計方案中使用的就是透明裝飾模式,在客戶端中存在如下程式碼片段:

……

Component component,componentSB,componentBB; //全部使用抽象構件定義

component = new Window();

componentSB = new ScrollBarDecorator(component);

componentBB = new BlackBorderDecorator(componentSB);

componentBB.display();

……

      使用抽象構件型別Component定義全部具體構件物件和具體裝飾物件,客戶端可以一致地使用這些物件,因此符合透明裝飾模式的要求。

      透明裝飾模式可以讓客戶端透明地使用裝飾之前的物件和裝飾之後的物件,無須關心它們的區別,此外,還可以對一個已裝飾過的物件進行多次裝飾,得到更為複雜、功能更為強大的物件。在實現透明裝飾模式時,要求具體裝飾類的operation()方法覆蓋抽象裝飾類的operation()方法,除了呼叫原有物件的operation()外還需要呼叫新增的addedBehavior()方法來增加新行為,

(2)半透明裝飾模式

      透明裝飾模式的設計難度較大,而且有時我們需要單獨呼叫新增的業務方法。為了能夠呼叫到新增方法,我們不得不用具體裝飾型別來定義裝飾之後的物件,而具體構件型別還是可以使用抽象構件型別來定義,這種裝飾模式即為半透明裝飾模式,也就是說,對於客戶端而言,具體構件型別無須關心,是透明的;但是具體裝飾型別必須指定,這是不透明的。如本節前面所提到的檔案物件功能增加例項,為了能夠呼叫到在Approver中新增方法approve(),客戶端程式碼片段如下所示:

……

Document  doc; //使用抽象構件型別定義

doc = new PurchaseRequest();

Approver newDoc; //使用具體裝飾型別定義

newDoc = new Approver(doc);

……

      半透明裝飾模式可以給系統帶來更多的靈活性,設計相對簡單,使用起來也非常方便;但是其最大的缺點在於不能實現對同一個物件的多次裝飾,而且客戶端需要有區別地對待裝飾之前的物件和裝飾之後的物件。在實現半透明的裝飾模式時,我們只需在具體裝飾類中增加一個獨立的addedBehavior()方法來封裝相應的業務處理,由於客戶端使用具體裝飾型別來定義裝飾後的物件,因此可以單獨呼叫addedBehavior()方法來擴充套件系統功能。

 

思考

為什麼半透明裝飾模式不能實現對同一個物件的多次裝飾?

12.5 裝飾模式注意事項

      在使用裝飾模式時,通常我們需要注意以下幾個問題:

(1) 儘量保持裝飾類的介面與被裝飾類的介面相同,這樣,對於客戶端而言,無論是裝飾之前的物件還是裝飾之後的物件都可以一致對待。這也就是說,在可能的情況下,我們應該儘量使用透明裝飾模式。

(2) 儘量保持具體構件類ConcreteComponent是一個“輕”類,也就是說不要把太多的行為放在具體構件類中,我們可以通過裝飾類對其進行擴充套件。

(3) 如果只有一個具體構件類,那麼抽象裝飾類可以作為該具體構件類的直接子類。如圖12-6所示:

12-6 沒有抽象構件類的裝飾模式

12.6 裝飾模式總結

      裝飾模式降低了系統的耦合度,可以動態增加或刪除物件的職責,並使得需要裝飾的具體構件類和具體裝飾類可以獨立變化,以便增加新的具體構件類和具體裝飾類。在軟體開發中,裝飾模式應用較為廣泛,例如在JavaIO中的輸入流和輸出流的設計、javax.swing包中一些圖形介面構件功能的增強等地方都運用了裝飾模式。

1.主要優點

      裝飾模式的主要優點如下:

(1) 對於擴充套件一個物件的功能,裝飾模式比繼承更加靈活性,不會導致類的個數急劇增加。

(2) 可以通過一種動態的方式來擴充套件一個物件的功能,通過配置檔案可以在執行時選擇不同的具體裝飾類,從而實現不同的行為。

(3) 可以對一個物件進行多次裝飾,通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創造出很多不同行為的組合,得到功能更為強大的物件。

(4) 具體構件類與具體裝飾類可以獨立變化,使用者可以根據需要增加新的具體構件類和具體裝飾類,原有類庫程式碼無須改變,符合“開閉原則”。

2.主要缺點

      裝飾模式的主要缺點如下:

(1) 使用裝飾模式進行系統設計時將產生很多小物件,這些物件的區別在於它們之間相互連線的方式有所不同,而不是它們的類或者屬性值有所不同,大量小物件的產生勢必會佔用更多的系統資源,在一定程式上影響程式的效能。

(2) 裝飾模式提供了一種比繼承更加靈活機動的解決方案,但同時也意味著比繼承更加易於出錯,排錯也很困難,對於多次裝飾的物件,除錯時尋找錯誤可能需要逐級排查,較為繁瑣。

3.適用場景

      在以下情況下可以考慮使用裝飾模式:

(1) 在不影響其他物件的情況下,以動態、透明的方式給單個物件新增職責。

(2) 當不能採用繼承的方式對系統進行擴充套件或者採用繼承不利於系統擴充套件和維護時可以使用裝飾模式。不能採用繼承的情況主要有兩類:第一類是系統中存在大量獨立的擴充套件,為支援每一種擴充套件或者擴充套件之間的組合將產生大量的子類,使得子類數目呈爆炸性增長;第二類是因為類已定義為不能被繼承(如Java語言中的final類)。

 

練習

Sunny軟體公司欲開發了一個資料加密模組,可以對字串進行加密。最簡單的加密演算法通過對字母進行移位來實現,同時還提供了稍複雜的逆向輸出加密,還提供了更為高階的求模加密。使用者先使用最簡單的加密演算法對字串進行加密,如果覺得還不夠可以對加密之後的結果使用其他加密演算法進行二次加密,當然也可以進行第三次加密。試使用裝飾模式設計該多重加密系統。

【作者:劉偉 http://blog.csdn.net/lovelion

相關文章