裝飾模式和AOP

3.3  裝飾模式和AOP

        裝飾模式和AOP在思想上有共同之處。可能有些朋友還不太瞭解AOP,下面先簡單介紹一下AOP的基礎知識。
1:什麼是AOP——面向方面編程
        AOP是一種編程範式,提供從另一個角度來考慮程序結構以完善面向對象編程(OOP)。
        在面向對象開發中,考慮系統的角度通常是縱向的,比如我們經常畫出的如下的系統架構圖,默認都是從上到下,上層依賴於下層,如圖5所示:

                                          圖5  系統架構圖示例圖
        而在每個模塊內部呢?就拿大家都熟悉的三層架構來說,也是從上到下來考慮的,通常是表現層調用邏輯層,邏輯層調用數據層,如圖6所示:


圖6  三層架構示意圖

        慢慢的,越來越多的人發現,在各個模塊之中,存在一些共性的功能,比如日誌管理、事務管理等等,如圖7所示:
 

                                         圖7  共性功能示意圖

        這個時候,在思考這些共性功能的時候,是從橫向在思考問題,與通常面向對象的縱向思考角度不同,很明顯,需要有新的解決方案,這個時候AOP站出來了。
        AOP爲開發者提供了一種描述橫切關注點的機制,並能夠自動將橫切關注點織入到面向對象的軟件系統中,從而實現了橫切關注點的模塊化。
        AOP能夠將那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任,例如事務處理、日誌管理、權限控制等,封裝起來,便於減少系統的重複代碼,降低模塊間的耦合度,並有利於未來的可操作性和可維護性。
        AOP之所以強大,就是因爲它能夠自動把橫切關注點的功能模塊,自動織入回到軟件系統中,這是什麼意思呢?
        先看看沒有AOP,在常規的面向對象系統中,對這種共性的功能如何處理,大都是把這些功能提煉出來,然後在需要用到的地方進行調用,只畫調用通用日誌的公共模塊,其它的類似,就不去畫了,如圖8所示:


                    圖8  調用公共功能示意圖

        看清楚,是從應用模塊中主動去調用公共模塊,也就是應用模塊要很清楚公共模塊的功能,還有具體的調用方法才行,應用模塊是依賴於公共模塊的,是耦合的,這樣一來,要想修改公共模塊就會很困難了,牽一而發百。
        看看有了AOP會怎樣,還是畫個圖來說明,如圖9所示:


                 圖9  AOP的調用示意圖

        乍一看,跟上面不用AOP沒有什麼區別嘛,真的嗎?看得仔細點,有一個非常非常大的改變,就是所有的箭頭方向反過來了,原來是應用系統主動去調用各個公共模塊的,現在變成了各個公共模塊主動織入回到應用系統。
        不要小看這一點變化,這樣一來應用系統就不需要知道公共功能模塊,也就是應用系統和公共功能解耦了。公共功能會在合適的時候,由外部織入回到應用系統中,至於誰來實現這樣的功能,以及如何實現不再我們的討論之列,我們更關注這個思想。
        如果按照裝飾模式來對比上述過程,業務功能對象就可以被看作是被裝飾的對象,而各個公共的模塊就好比是裝飾器,可以透明的來給業務功能對象增加功能。
        所以從某個側面來說,裝飾模式和AOP要實現的功能是類似的,只不過AOP的實現方法不同,會更加靈活,更加可配置;另外AOP一個更重要的變化是思想上的變化——“主從換位”,讓原本主動調用的功能模塊變成了被動等待,甚至毫不知情的情況下被織入了很多新的功能。

2:使用裝飾模式做出類似AOP的效果
        下面來演示一下使用裝飾模式,把一些公共的功能,比如權限控制,日誌記錄,透明的添加回到業務功能模塊中去,做出類似AOP的效果。
(1)首先定義業務接口
        這個接口相當於裝飾模式的Component。注意這裏使用的是接口,而不像前面一樣使用的是抽象類,雖然使用抽象類的方式來定義組件是裝飾模式的標準實現方式,但是如果不需要爲子類提供公共的功能的話,也是可以實現成接口的,這點要先說明一下,免得有些朋友會認爲這就不是裝飾模式了,示例代碼如下:

  1. /** 
  2. * 商品銷售管理的業務接口 
  3. */  
  4. public interface GoodsSaleEbi {  
  5.     /** 
  6.      * 保存銷售信息,本來銷售數據應該是多條,太麻煩了,爲了演示,簡單點 
  7.      * @param user 操作人員 
  8.      * @param customer 客戶 
  9.      * @param saleModel 銷售數據 
  10.      * @return 是否保存成功 
  11.      */  
  12.     public boolean sale(String user,String customer,  
  13. SaleModel saleModel);  
  14. }  

 

順便把封裝業務數據的對象也定義出來,很簡單,示例代碼如下:

  1. /** 
  2. * 封裝銷售單的數據,簡單的示意一些 
  3. */  
  4. public class SaleModel {  
  5.     /** 
  6.     * 銷售的商品 
  7.     */  
  8.     private String goods;  
  9.     /** 
  10.     * 銷售的數量 
  11.     */  
  12.     private int saleNum;  
  13.     public String getGoods() {    
  14.         return goods;     
  15.     }  
  16.     public void setGoods(String goods) {  
  17.         this.goods = goods;   
  18.     }  
  19.     public int getSaleNum() {  
  20.         return saleNum;  
  21.     }  
  22.     public void setSaleNum(int saleNum) {  
  23.         this.saleNum = saleNum;  
  24.     }  
  25.     public String toString(){  
  26.         return "商品名稱="+goods+",購買數量="+saleNum;  
  27.     }  
  28. }  

(2)定義基本的業務實現對象,示例代碼如下:

  1. public class GoodsSaleEbo implements GoodsSaleEbi{  
  2.     public boolean sale(String user,String customer,   
  3.                       SaleModel saleModel) {  
  4.         System.out.println(user+"保存了"  
  5.                                  +customer+"購買 "+saleModel+" 的銷售數據");  
  6.         return true;  
  7.     }  
  8. }  

 

(3)接下來該來實現公共功能了,把這些公共功能實現成爲裝飾器,那麼需要給它們定義一個抽象的父類,示例如下:

  1. /** 
  2. * 裝飾器的接口,需要跟被裝飾的對象實現同樣的接口 
  3. */  
  4. public abstract class Decorator implements GoodsSaleEbi{  
  5.     /** 
  6.                * 持有被裝飾的組件對象 
  7.            */  
  8.     protected GoodsSaleEbi ebi;  
  9.     /** 
  10.      * 通過構造方法傳入被裝飾的對象 
  11.      * @param ebi被裝飾的對象 
  12.      */  
  13.     public Decorator(GoodsSaleEbi ebi){  
  14.         this.ebi = ebi;  
  15.     }  
  16. }  

(4)實現權限控制的裝飾器
先檢查是否有運行的權限,如果有就繼續調用,如果沒有,就不遞歸調用了,而是輸出沒有權限的提示,示例代碼如下:

  1. /** 
  2.  * 實現權限控制 
  3.  */  
  4. public class CheckDecorator extends Decorator{  
  5.     public CheckDecorator(GoodsSaleEbi ebi){  
  6.         super(ebi);  
  7.     }  
  8.     public boolean sale(String user,String customer  
  9.         , SaleModel saleModel) {  
  10.         //簡單點,只讓張三執行這個功能  
  11.         if(!"張三".equals(user)){  
  12.             System.out.println("對不起"+user  
  13.                 +",你沒有保存銷售單的權限");  
  14.             //就不再調用被裝飾對象的功能了  
  15.             return false;  
  16.         }else{  
  17.             return this.ebi.sale(user,customer,saleModel);  
  18.         }         
  19.     }  
  20. }  

(5)實現日誌記錄的裝飾器,就是在功能執行完成後記錄日誌即可,示例代碼如下:

  1. /** 
  2. * 實現日誌記錄 
  3. */  
  4. public class LogDecorator extends Decorator{  
  5.     public LogDecorator(GoodsSaleEbi ebi){  
  6.         super(ebi);  
  7.     }  
  8.     public boolean sale(String user,String customer,   
  9.         SaleModel saleModel) {  
  10.         //執行業務功能  
  11.         boolean f = this.ebi.sale(user, customer, saleModel);  
  12.   
  13.         //在執行業務功能過後,記錄日誌  
  14.         DateFormat df =   
  15.             new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");  
  16.         System.out.println("日誌記錄:"+user+"於"+  
  17.                 df.format(new Date())+"時保存了一條銷售記錄,客戶是"  
  18.                 +customer+",購買記錄是"+saleModel);  
  19.         return f;  
  20.     }  
  21. }  

(6)組合使用這些裝飾器
        在組合的時候,權限控制應該是最先被執行的,所以把它組合在最外面,日誌記錄的裝飾器會先調用原始的業務對象,所以把日誌記錄的裝飾器組合在中間。
        前面講過,裝飾器之間最好不要有順序限制,但是在實際應用中,要根據具體的功能要求來,有需要的時候,也可以有順序的限制,但應該儘量避免這種情況。
        此時客戶端測試代碼示例如下:

  1. public class Client {  
  2.     public static void main(String[] args) {  
  3.         //得到業務接口,組合裝飾器  
  4.         GoodsSaleEbi ebi = new CheckDecorator(  
  5.                 new LogDecorator(  
  6.                 new GoodsSaleEbo()));  
  7.         //準備測試數據  
  8.         SaleModel saleModel = new SaleModel();  
  9.         saleModel.setGoods("Moto手機");  
  10.         saleModel.setSaleNum(2);  
  11.         //調用業務功能  
  12.         ebi.sale("張三","張三丰", saleModel);  
  13.         ebi.sale("李四","張三丰", saleModel);  
  14.     }  
  15. }  

 

運行結果如下:
  

 

        好好體會一下,是不是也在沒有驚動原始業務對象的情況下,給它織入了新的功能呢?也就是說是在原始業務不知情的情況下,給原始業務對象透明的增加了新功能,從而模擬實現了AOP的功能。
        事實上,這種做法,完全可以應用在項目開發上,在後期爲項目的業務對象添加數據檢查、權限控制、日誌記錄等功能,就不需要在業務對象上去處理這些功能了,業務對象可以更專注於具體業務的處理。

3.4  裝飾模式的優缺點

  • 比繼承更靈活
        從爲對象添加功能的角度來看,裝飾模式比繼承來得更靈活。繼承是靜態的,而且一旦繼承是所有子類都有一樣的功能。而裝飾模式採用把功能分離到每個裝飾器當中,然後通過對象組合的方式,在運行時動態的組合功能,每個被裝飾的對象,最終有哪些功能,是由運行期動態組合的功能來決定的。
  • 更容易複用功能
        裝飾模式把一系列複雜的功能,分散到每個裝飾器當中,一般一個裝飾器只實現一個功能,這樣實現裝飾器變得簡單,更重要的是這樣有利於裝飾器功能的複用,可以給一個對象增加多個同樣的裝飾器,也可以把一個裝飾器用來裝飾不同的對象,從而複用裝飾器的功能。
  • 簡化高層定義
        裝飾模式可以通過組合裝飾器的方式,給對象增添任意多的功能,因此在進行高層定義的時候,不用把所有的功能都定義出來,而是定義最基本的就可以了,可以在使用需要的時候,組合相應的裝飾器來完成需要的功能。
  • 會產生很多細粒度對象
        前面說了,裝飾模式是把一系列複雜的功能,分散到每個裝飾器當中,一般一個裝飾器只實現一個功能,這樣會產生很多細粒度的對象,而且功能越複雜,需要的細粒度對象越多。

 

3.5  思考裝飾模式

1:裝飾模式的本質
        裝飾模式的本質:動態組合
        動態是手段,組合纔是目的。這裏的組合有兩個意思,一個是動態功能的組合,也就是動態進行裝飾器的組合;另外一個是指對象組合,通過對象組合來實現爲被裝飾對象透明的增加功能。
        但是要注意,裝飾模式不僅僅可以增加功能,也可以控制功能的訪問,可以完全實現新的功能,還可以控制裝飾的功能是在被裝飾功能之前還是之後來運行等。
        總之,裝飾模式是通過把複雜功能簡單化,分散化,然後在運行期間,根據需要來動態組合的這麼一個模式。

2:何時選用裝飾模式
       建議在如下情況中,選用裝飾模式:

  • 如果需要在不影響其它對象的情況下,以動態、透明的方式給對象添加職責,可以使用裝飾模式,這幾乎就是裝飾模式的主要功能
  • 如果不合適使用子類來進行擴展的時候,可以考慮使用裝飾模式,因爲裝飾模式是使用的“對象組合”的方式。所謂不適合用子類擴展的方式,比如:擴展功能需要的子類太多,造成子類數目呈爆炸性增長。

 

3.6  相關模式

  • 裝飾模式與適配器模式
        這是兩個沒有什麼關聯的模式,放到一起來說,是因爲它們有一個共同的別名:Wrapper。
        這兩個模式功能上是不一樣的,適配器模式是用來改變接口的,而裝飾模式是用來改變對象功能的。
  • 裝飾模式與組合模式
        這兩個模式有相似之處,都涉及到對象的遞歸調用,從某個角度來說,可以把裝飾看成是隻有一個組件的組合。
        但是它們的目的完全不一樣,裝飾模式是要動態的給對象增加功能;而組合模式是想要管理組合對象和葉子對象,爲它們提供一個一致的操作接口給客戶端,方便客戶端的使用。
  • 裝飾模式與策略模式
        這兩個模式可以組合使用。
        策略模式也可以實現動態的改變對象的功能,但是策略模式只是一層選擇,也就是根據策略選擇一下具體的實現類而已。而裝飾模式不是一層,而是遞歸調用,無數層都可以,只要組合好裝飾器的對象組合,那就可以依次調用下去,所以裝飾模式會更靈活。
        而且策略模式改變的是原始對象的功能,不像裝飾模式,後面一個裝飾器,改變的是經過前一個裝飾器裝飾過後的對象,也就是策略模式改變的是對象的內核,而裝飾模式改變的是對象的外殼。
        這兩個模式可以組合使用,可以在一個具體的裝飾器裏面使用策略模式,來選擇更具體的實現方式。比如前面計算獎金的另外一個問題就是參與計算的基數不同,獎金的計算方式也是不同的。舉例來說:假設張三和李四參與同一個獎金的計算,張三的銷售總額是2萬元,而李四的銷售額是8萬元,它們的計算公式是不一樣的,假設獎金的計算規則是,銷售額在5萬以下,統一3%,而5萬以上,5萬內是4%,超過部分是6%。
        參與同一個獎金的計算,這就意味着可以使用同一個裝飾器,但是在裝飾器的內部,不同條件下計算公式不一樣,那麼怎麼選擇具體的實現策略呢?自然使用策略模式就好了,也就是裝飾模式和策略模式組合來使用。
  • 裝飾模式與模板方法模式
        這是兩個功能上有相似點的模式。
        模板方法模式主要應用在算法骨架固定的情況,那麼要是算法步驟不固定呢,也就是一個相對動態的算法步驟,就可以使用裝飾模式了,因爲在使用裝飾模式的時候,進行裝飾器的組裝,其實也相當於是一個調用算法步驟的組裝,相當於是一個動態的算法骨架。
        既然裝飾模式可以實現動態的算法步驟的組裝和調用,那麼把這些算法步驟固定下來,那就是模板方法模式實現的功能了,因此裝飾模式可以模擬實現模板方法模式的功能。
        但是請注意,僅僅只是可以模擬功能而已,兩個模式的設計目的、原本的功能、本質思想等都是不一樣的。

 裝飾模式結束,謝謝觀賞

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章