設計模式-代理模式(和裝飾模式的真正區別)
最近有時間,學習了一下設計模式,發現了一個問題,代理模式(靜態代理)在寫法和結構上,基本和裝飾器是一樣的。
由此引發了對這兩者的真正區別的思考,網上搜索了許許多多的答案(雖然都有一定的道理,個人覺得都沒有說到真正的重點) :
1 . 有的人說是結構上不同,代理和真實物件之間的的關係通常在編譯時就已經確定了,而裝飾器能夠在執行時遞迴地被構造(我個人完全反對這種說法);
2 . 裝飾器模式為了增強功能;而代理模式是為了加以控制,代理類對被代理的物件有控制權,決定其執行或者不執行。(大名鼎鼎的菜鳥教程這樣解釋);
3 . 甚至還有人說裝飾器模式用於新增新方法;而代理模式則用於增強原方法(那為什麼叫代理?)。
代理模式(靜態)與裝飾者雖然在結構上基本上一模一樣,但兩者卻有真正區別,我認為是 : 目的不一樣,關注的重心不一樣。
代理模式目的 : 讓原有物件被代理,我們的目的是讓使用者儘可能的感受不到原有物件,原有物件的行為或額外的動作交由代理物件完成。(完成代理模式的真正意義)
裝飾器模式目的 : 讓原有物件被增強,我們的目的通常是得到由原有物件被增強後的裝飾器物件行為。(完成裝飾器模式的真正意義)
代理模式關注重心 : 主要功能不變,代理物件只是幫忙代理或稍加擴充套件原有物件的行為,功能上主要關心原有物件所具有的行為。(最終主要功能仍然由原有物件決定)
裝飾器模式關注重心 : 主要功能增強,使用裝飾器目的就是為了增強,功能上更關心裝飾增加後的行為。(最終主要功能由裝飾物件決定)
靜態代理
靜態代理的角色分為 : 抽象行為角色,委託人,代理人。基本寫法如下 :
抽象行為角色 : 是委託人和代理人的共同介面。這裡我們叫它抽象主題(Subject) :
package name.ealen.proxy.designPattern.staticProxy; /** * Created by EalenXie on 2018/11/2 10:16. */ public interface Subject { public void operation(); }
委託人 : 也就是我上面一直說的原有物件,真正被代理的物件,也叫做代理元。這裡我們叫它真實主題 (RealSubject):
package name.ealen.proxy.designPattern.staticProxy; /** * Created by EalenXie on 2018/11/2 10:17. */ public class RealSubject implements Subject { @Override public void operation() { System.out.println("真實物件 : 重要操作"); } }
代理人 : 代理角色,由它去代理原有物件。它包含被代理物件的引用,這裡叫它代理主題(ProxySubject):
package name.ealen.proxy.designPattern.staticProxy; /** * Created by EalenXie on 2018/11/2 10:18. */ public class ProxySubject implements Subject { private Subject subject; public ProxySubject(Subject subject) { this.subject = subject; } /** * 目的 : 代理真實物件完成方法呼叫,代理可以進行相對不重要的行為擴充套件 */ @Override public void operation() { before(); subject.operation(); after(); } private void after() { System.out.println("代理人 : 真實物件的操作完成了"); } private void before() { System.out.println("代理人 : 開始完成真實物件的操作"); } }
測試程式碼 :
/** * 靜態代理 */ @Test public void staticProxy() { Subject realSubject = new RealSubject(); //一個真實物件 Subject proxy = new ProxySubject(realSubject); //一個代理人,指定要代理的真實物件,型別只能是Subject及其子類 proxy.operation(); //整個操作由代理人幫真實物件完成,代理人還做了操作說明 }
結果如下 :
動態代理
動態代理主要依賴Java反射機制實現,基本寫法如下 :
抽象行為角色 : 是委託人和代理人的共同介面。這裡我們叫它抽象主題(DynamicSubject) :
package name.ealen.proxy.designPattern.dynamicProxy; /** * Created by EalenXie on 2018/11/2 15:35. */ public interface DynamicSubject { public void operation() ; }
一個被代理物件 ,這裡叫他真實物件(DynamicRealSubject) :
package name.ealen.proxy.designPattern.dynamicProxy; /** * Created by EalenXie on 2018/11/2 12:56. */ public class DynamicRealSubject implements DynamicSubject { @Override public void operation() { System.out.println("真實物件 : 重要操作"); } }
代理的呼叫處理類(ProxyHandler) ,主要實現反射的介面 InvocationHandler ,這裡可以看出該處理類設計上和被代理物件並沒有任何的直接聯絡 :
package name.ealen.proxy.designPattern.dynamicProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * Created by EalenXie on 2018/11/2 12:57. * 代理的呼叫處理類 */ public class ProxyHandler implements InvocationHandler { private Object realSubject; //指定被代理的真實物件 public ProxyHandler(Object realSubject) { this.realSubject = realSubject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { before(); //自定義邏輯 Object result = method.invoke(realSubject, args); //呼叫真實物件的操作 after(); //自定義邏輯 return result; } private void after() { System.out.println("代理人 : 真實物件的操作完成了"); } private void before() { System.out.println("代理人 : 開始完成真實物件的操作"); } }
那麼如何得到我們的代理物件呢?答案是基於反射類Proxy,既然是動態代理,那麼代理的呼叫處理類,是可以代理任何型別的物件。請看如下測試類 :
/** * 動態代理,基於反射類實現 */ @Test public void dynamicProxy() { //一個真實物件 DynamicSubject subject = new DynamicRealSubject(); //動態代理處理邏輯 ,基於反射類InvocationHandler實現,指定要代理的真實物件,可以是任何型別 InvocationHandler proxyHandler = new ProxyHandler(subject); //一個代理人,例項化基於反射類Proxy實現。 DynamicSubject proxy = (DynamicSubject) Proxy.newProxyInstance(DynamicSubject.class.getClassLoader(), subject.getClass().getInterfaces(), proxyHandler); //整個操作由代理人幫真實物件完成,代理人還做了操作說明 proxy.operation(); }
只需要在ProxyHandler中動態的傳入任何我們需要代理的物件,然後Proxy呼叫newProxyInstance,強轉即可得到我們的代理物件,結果如下 :