設計模式:代理模式與裝飾模式

一、裝飾者模式與代理模式 (靜態代理) 

 在平常開發裏面,咱們常常須要給某個類的方法增長加某些特定的功能。java

       例如:有嬰兒,嬰兒會吃飯和走動,如如下類node

 1 package com.scl.designpattern.proxy;
 2 
 3 //嬰兒類
 4 public class Child implements Human
 5 {
 6     public void eat()
 7     {
 8         System.out.println("eat something....");
 9     }
10 
11     @Override
12     public void run()
13     {
14         System.out.println("Child run very slow");
15     }
16 }
嬰兒類

  忽然有一天,家長髮現不行,孩子不能隨便吃東西,並且吃飯前必定要洗手。可是孩子過小(被委託方),不會本身洗手。家長(Client 端)又無法照顧孩子。那簡單,找個保姆照顧孩子! 讓保姆類和嬰兒類共同實現同一個接口,讓保姆類全程管理小孩,同時在家長眼裏,只要看到保姆在幫孩子洗手就能夠了。因而,有如下內容。編程

 1 package com.scl.designpattern.proxy;
 2 
 3 //保姆類
 4 public class BabySitter implements Human
 5 {
 6 
 7     @Override
 8     public void eat()
 9     {
10 
11     }
12 
13     @Override
14     public void run()
15     {
16 
17     }
18 
19 }
保姆類

     如今保姆已經有了,孩子也有了,怎麼把孩子跟保姆關聯起來。讓保姆給相應的孩紙洗手。因而保姆類更改以下
設計模式

 1 package com.scl.designpattern.proxy;
 2 
 3 //保姆類
 4 public class BabySitter implements Human
 5 {
 6     private Human human;
 7 
 8     public BabySitter(Human human)
 9     {
10         this.human = human;
11     }
12 
13     @Override
14     public void eat()
15     {
16         // 添加washHand的方法
17         this.washHandForChild();
18         human.eat();
19     }
20 
21     @Override
22     public void run()
23     {
24 
25     }
26 
27     public void washHandForChild()
28     {
29         System.out.println("help the child to wash his hands");
30     }
31 }
保姆與嬰兒類關聯

    好,那麼家長就是給孩紙找了個保姆代理,讓他附加了一些嬰兒作不了事。同時家長也沒有強迫孩紙本身學會洗手(不更改Child類)app

 1 package com.scl.designpattern.proxy;
 2 
 3 //客戶端
 4 public class Client
 5 {
 6     public static void main(String[] args)
 7     {
 8         Human human = new BabySitter(new Child());
 9         human.eat();
10     }
11 }
家長客戶端代碼

  以上就是一個簡單的裝飾模式,來看一下這一塊完整的類圖。ide

 

  裝飾模式的一個很重要特色就是,在客戶端能夠看到抽象對象的實例,如Human human = new BabySitter(new Child()); 由於裝飾模式經過聚合方式,把內容整合到裝飾類裏面了。性能

    裝飾者模式可以使用裝飾類對抽象對象進行裝飾。假如來了個OldMan類手腳不利索。保姆類BabySitter一樣可以勝任這個OldMan的飯前洗手操做。this

  例子想了好久,也看了很多別人的博客。發現一個很使人迷惑的問題:爲何我用想的代理模式,確是別人口中的裝飾模式?裝飾者模式和代理模式有什麼區別?。大體的代理模式類圖以下:編碼

  

  由該類圖可知,以上BabySitter代碼應該以下:spa

 1 package com.scl.designpattern.proxy;
 2 
 3 //保姆類
 4 public class BabySitter implements Human
 5 {    
 6     private Child child;
 7 
 8     public BabySitter()
 9     {
10         child = new Child();
11     }
12 
13     @Override
14     public void eat()
15     {
16         // 添加washHand的方法
17         this.washHandForChild();
18         human.eat();
19     }
20 
21     @Override
22     public void run()
23     {
24 
25     }
26 
27     public void washHandForChild()
28     {
29         System.out.println("help the child to wash his hands");
30     }
31 }
代理模式下的代理類
 1 package com.scl.designpattern.proxy;
 2 
 3 //客戶端
 4 public class Client
 5 {
 6     public static void main(String[] args)
 7     {
 8         Human human = new BabySitter();
 9         human.eat();
10     }
11 }
代理模式下的客戶端

 裝飾者模式和代理模式的最後運行的結果都是同樣的,顯示以下

 由代理模式代碼可知,客戶端不關心代理類了哪一個類。但代碼控制了客戶端對委託類的訪問。客戶端代碼表現爲 Human human = new BabySitter( );

 因此資料上都說了,裝飾模式主要是強調對類中代碼的拓展,而代理模式則偏向於委託類的訪問限制。二者都同樣擁有抽象角色(接口)、真實角色(委託類)、代理類 。

 因爲代理類實現了抽象角色的接口,致使代理類沒法通用。若有天,一個有錢人養了只小猩猩,他要一個保姆在猩猩吃東西前,幫猩猩洗手....保姆根本不懂猩猩的特性(跟猩猩類不是同一類型的,保姆屬於Human類,而猩猩可能屬於Animal類型。),但洗手這個方法是不變的,用水洗。能不能找一個代理說既能夠照看人吃飯前洗手也能夠照看猩猩吃飯前洗手?

用機器人吧... (編不下去了)。要實現這種功能,必須讓代理類與特定的接口分離。在代理的時候可以瞭解每一個委託的特性,這就有可能了。這時候動態代理就出現了。

二、動態代理 

 2.1 基於繼承接口類的動態代理 

    如靜態代理的內容所描述的,靜態代理受限於接口的實現。動態代理就是經過使用反射,動態地獲取抽象接口的類型,從而獲取相關特性進行代理。因動態代理可以爲全部的委託方進行代理,所以給代理類起個通用點的名字HealthHandle。先看保姆類能夠變成什麼樣。

 1 package com.scl.designpattern.proxy.dynamic;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 import java.lang.reflect.Method;
 5 import java.lang.reflect.Proxy;
 6 
 7 public class HealthHandle implements InvocationHandler
 8 {
 9 
10     private Object proxyTarget;
11 
12     public Object getProxyInstance(Object target)
13     {
14         this.proxyTarget = target;
15         return Proxy.newProxyInstance(proxyTarget.getClass().getClassLoader(), proxyTarget.getClass().getInterfaces(), this);
16     }
17 
18     @Override
19     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
20     {
21         Object methodObject = null;
22         System.out.println("proxy washing hand start");
23         methodObject = method.invoke(proxyTarget, args);
24         System.out.println("proxy washing hand end");
25         return methodObject;
26     }
27 }
由保姆類轉化而來的動態代理類
 1 package com.scl.designpattern.proxy.dynamic;
 2 
 3 import java.lang.reflect.Proxy;
 4 
 5 //客戶端
 6 public class Client
 7 {
 8     public static void main(String[] args)
 9     {
10         HealthHandle h = new HealthHandle();
11         Human human = (Human) h.getProxyInstance(new Child());
12         human.eat();
13         human.run();
14     }
15 }
動態代理模式下的客戶端代碼

  首先,使用動態代理

       1. 必須實現InvocationHandler接口,訂立一個契約標識該類爲一個動態代理執行類。

       2. InvocationHandler接口內有一實現方法簽名以下: public Object invoke(Object proxy, Method method, Object[] args) 。使用時須要重寫這個方法

       3. 獲取代理類,須要使用 Proxy.newProxyInstance(Clas loader, Class<?>[] interfaces, InvocationHandler h) 這個方法去獲取Proxy對象(Proxy類類型的實例,非Child類類型實例)。

    先看獲取代理對象簽名解析以下:

      //Clas loader : 類的加載器   //Class<?>[] interfaces : 委託類實現的接口,保證代理類返回的是同一個實現接口下的類型,保持代理類與抽象角色行爲的一致 //invocationHandler, 該類最重要的一環。一個設計模式:策略模式.即告訴代理類,代理類遇到某個委託類的方法時該調用哪一個類下的invoke方法。 Proxy.newProxyInstance(Class loader, Class<?>[] interfaces, InvocationHandler h) 

  再看實現InvocationHandler方法下的簽名 public Object invoke(Object proxy, Method method, Object[] args)

      //第一個參數爲Proxy類類型實例,如匿名的$proxy實例   //第二個參數爲委託類的方法對象//第三個參數爲委託類的方法參數
  //返回類型爲委託類某個方法的執行結果 public Object invoke(Object proxy, Method method, Object[] args)
  
總的來講這個方法就是動態獲取委託類裏面的方法,在調用委託類的方法時在這個方法內進行拓展。 經過上面的
Proxy.newProxyInstance方法,告訴底層代碼,該去哪一個類裏面執行invoke方法。

  由上面的描述,在客戶端代碼的調用後,結果以下: 

     

     因而可知,在代理類對系統下每一個方法的調用時,都會去調用invocationHandler裏面的invoke方法. 包括裏面的eat和run方法,可見受做用的範圍是多廣:每一個方法都受代理類做用了。由例子能夠看出,若是要實現咱們想要的方法上面添加特定的代理,能夠經過invoke方法裏面的方法反射獲取method對象方法名稱便可實現(但這樣會產生硬編碼)。修改可以下:

 1     @Override
 2     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
 3     {
 4         Object methodObject = null;
 5         if (method.getName().equals("eat"))
 6         {
 7             System.out.println("proxy washing hand start");
 8             methodObject = method.invoke(proxyTarget, args);
 9             System.out.println("proxy washing hand end");
10         }
11         else
12         {
//不使用第一個proxy參數做爲參數,不然會形成死循環
13 methodObject = method.invoke(proxyTarget, args); 14 } 15 return methodObject; 16 }

    由此,咱們能夠猜測出來動態代理在開發中的用法:

  1. 監控程序下執行某個方法的耗時、性能及日誌的編寫

  2. 監控程序方法的權限,再也不單一地實行Controler→ServiceImpl→DaoImpl這種縱向編程。增長了在同一層面間橫向編程的可能(切面編程:AOP)


  
另外有幾點必須指出的內容:

   1. 基於接口類的動態代理模式,必須具有抽象角色、委託類、代理三個基本角色。委託類和代理類必須由抽象角色衍生出來,不然沒法使用該模式

   2.在使用Invoke方法的時候,須要對method的可訪問性進行加工嗎?不須要,動態代理模式最後返回的是具備抽象角色(頂層接口)的對象。在委託類內被private或者protected關鍵修飾的方法將不會予以調用,即便容許調用。也沒法在客戶端使用代理類轉換成子類接口,對方法進行調用。

      

 

 3. 在invoke方法內爲何不使用第一個參數進行執行回調。

    在客戶端使用getProxyInstance(new Child( ))時,JDK會返回一個$proxy的實例,實例內有InvokecationHandler對象及動態繼承下來的方法eat。客戶端調用了eat方法,有以下操做:①JDK先查找$proxy實例內的handler對象 ②執行handler內的invoke方法。 根據public Object invoke(Object proxy, Method method, Object[] args)這個方法簽名第一個參數proxy就是對應着下圖的$proxy實例。若是在invoke內使用method.invoke(proxy,args) ,會出現這樣一條方法鏈,eat→invoke→eat→invoke...,最終致使堆棧溢出。

   

2.2 基於子類的動態代理

  假設目前有個項目,A公司寫了一個Computer類已經整合到某一個jar包裏面,這個類是個普通類沒有任何抽象角色(沒有實現任何接口)。可是B公司須要對Computer類內的方法進行增強、拓展。那B公司怎麼辦?目前只有兩個方法:一、拿A公司代碼反編譯,而後修改A公司裏面這個類的方法。 二、拿A公司代碼反編譯,讓A公司Computer類進行抽象化,而後B公司使用基於接口的動態代理對方法進行拓展。很明顯,這兩個方法都是違背了面向對象設計的思想的 (①高內聚低耦合 ②開閉原則[OCP]:對拓展開放,對修改關閉)
  在這種狀況下基於子類的動態代理產生了。基於子類的動態代理不是由JDK內附功能。須要引入第三方的開發包:CGlib,使用這個子類的代理須要引入cglib-nodep.jar
CGLib在代碼上的使用基本和基於接口的動態代理同樣,使用方法極爲類似,僅表如今CGLib代理時使用的是方法攔截器MethodInterceptor。代碼使用以下:
 1 package com.scl.designpattern.proxy.dynamic.cglib;
 2 
 3 //嬰兒類
 4 public class Child
 5 {
 6     public void eat()
 7     {
 8         System.out.println("eat something....");
 9     }
10 
11     public void run()
12     {
13         System.out.println("Child run very slow");
14     }
15 
16     protected void breath()
17     {
18         System.out.println("Child breath slowly");
19     }
20 }
沒有抽象角色的Child類
 1 package com.scl.designpattern.proxy.dynamic.cglib;
 2 
 3 import java.lang.reflect.Method;
 4 
 5 import net.sf.cglib.proxy.Enhancer;
 6 import net.sf.cglib.proxy.MethodInterceptor;
 7 import net.sf.cglib.proxy.MethodProxy;
 8 
 9 public class HealthHandle implements MethodInterceptor
10 {
11 
12     private Object proxyTarget;
13 
14     public Object getProxyInstance(Object target)
15     {
16         this.proxyTarget = target;
17 
18         return Enhancer.create(target.getClass(), target.getClass().getInterfaces(), this);
19     }
20 
21     @Override
22     public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable
23     {
24         Object methodResult = null;
25         if (method.getName().equals("eat"))
26         {
27             System.out.println("wash hand before eat");
28             methodResult = method.invoke(proxyTarget, args);
29             methodProxy.invokeSuper(proxy, args);
30             System.out.println("wash hand after eat");
31         }
32         else
33         {
34             methodResult = method.invoke(proxyTarget, args);
35         }
36         return methodResult;
37     }
38 }
動態代理類
 1 package com.scl.designpattern.proxy.dynamic.cglib;
 2 
 3 //客戶端
 4 public class Client2
 5 {
 6     public static void main(String[] args)
 7     {
 8         HealthHandle h = new HealthHandle();
 9         Child child = (Child) h.getProxyInstance(new Child());
10         child.eat();
11         child.breath();
12     }
13 }
客戶端

   源碼Interceptor簽名以下

 1     /**
 2      * All generated proxied methods call this method instead of the original method.
 3      * The original method may either be invoked by normal reflection using the Method object,
 4      * or by using the MethodProxy (faster).      官方建議使用MethodProxy對方法進行調用
 5      * @param obj "this", the enhanced object     跟接口代理同樣,第一個參數爲Enhanced對象實例
 6      * @param method intercepted Method
 7      * @param args argument array; primitive types are wrapped
 8      * @param proxy used to invoke super (non-intercepted method); may be called
 9      * as many times as needed
10      * @throws Throwable any exception may be thrown; if so, super method will not be invoked
11      * @return any value compatible with the signature of the proxied method. Method returning void will ignore this value.
12      * @see MethodProxy
13      */    
14     public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;

另外要補充的兩點:

1. 方法攔截器對protected修飾的方法能夠進行調用

2. 官方推薦在對委託方法進行調用時使用MethodProxy對方法進行調用。這樣有兩個好處:①官方說速度比較快 ②在intercept內調用委託類方法時不用保存委託對象引用

以上爲本次對代理模式的總結,若有錯誤煩請指出糾正。轉載請註明出處。

相關文章
相關標籤/搜索