設計模式-代理模式
設計模式-代理模式
什麼是代理模式?
代理模式的定義:使用一個代理將物件包裝起來,然後用該代理取代原始物件。任何原始物件的呼叫都要通過代理。代理物件決定是否以及何時將方法呼叫轉到原始物件上。
通俗的來講代理模式就是我們生活中常見的中介。
舉個例子來說明:假如說我現在想買一輛二手車,雖然我可以自己去找車源,做質量檢測等一系列的車輛過戶流程,但是這確實太浪費我得時間和精力了。我只是想買一輛車而已為什麼我還要額外做這麼多事呢?於是我就通過中介公司來買車,他們來給我找車源,幫我辦理車輛過戶流程,我只是負責選擇自己喜歡的車,然後付錢就可以了。用圖表示如下:
為什麼要用代理模式?
- 中介隔離作用:在某些情況下,一個客戶類不想或者不能直接引用一個委託物件,而代理類物件可以在客戶類和委託物件之間起到中介的作用,其特徵是代理類和委託類實現相同的介面。
- 開閉原則,增加功能:代理類除了是客戶類和委託類的中介之外,我們還可以通過給代理類增加額外的功能來擴充套件委託類的功能,這樣做我們只需要修改代理類而不需要再修改委託類,符合程式碼設計的開閉原則。代理類主要負責為委託類預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後對返回結果的處理等。代理類本身並不真正實現服務,而是同過呼叫委託類的相關方法,來提供特定的服務。真正的業務功能還是由委託類來實現,但是可以在業務功能執行的前後加入一些公共的服務。例如我們想給專案加入快取、日誌這些功能,我們就可以使用代理類來完成,而沒必要開啟已經封裝好的委託類。
有哪幾種代理模式?
按照編譯時還是執行時,確定代理物件,可以分為靜態代理和動態代理兩種
靜態代理是由程式設計師建立或特定工具自動生成原始碼,在對其編譯。在程式設計師執行之前,代理類.class檔案就已經被建立了。
動態代理是在程式執行時通過反射機制動態建立的。
代理類和被代理類都要實現同一個介面
1.靜態代理
第一步:建立一個介面
public interface ClothFactory { public void produceCloth(); }
第二步:建立被代理類,實現介面
public class NikeClothFactory implementsClothFactory { public NikeClothFactory() { } @Override public void produceCloth() { System.out.println("Nike正在生產衣服"); } }
第三步:建立代理類,實現介面
public class ProxyClothFactory implements ClothFactory { private ClothFactory factory; //需要用被代理類來例項化factory public ProxyClothFactory(ClothFactory factory) { this.factory = factory; } @Override public void produceCloth() { System.out.println("代理工廠正在做準備工作"); factory.produceCloth(); System.out.println("代理類正在做收尾工作"); } }
第四步:編寫測試類
@Test public void test2() { NikeClothFactory nikeClothFactory=new NikeClothFactory(); ProxyClothFactory proxyNikeClothFactory=new ProxyClothFactory(nikeClothFactory); proxyNikeClothFactory.produceCloth(); }
結果
代理工廠正在做準備工作
Nike正在生產衣服
代理類正在做收尾工作
靜態代理總結:
優點:可以做到在符合開閉原則的情況下對目標物件進行功能擴充套件。
缺點:我們得為每一個服務都得建立代理類,工作量太大,不易管理。同時介面一旦發生改變,代理類也得相應修改。
2.動態代理
需要解決的問題
①如何根據載入到記憶體中的被代理類動態的去建立一個代理類及其物件(Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h))
ClassLoader loader
:指定當前目標物件使用的類載入器,獲取載入器的方法是固定的(loader.getClass().getClassLoader())Class<?>[] interfaces
:指定目標物件實現的介面的型別,使用泛型方式確認型別(loader.getClass().getInterfaces())InvocationHandler:
指定
動態處理器,
執行目標物件的方法時,會觸發事件處理器的方法(這個是解決問題②的關鍵),需要傳入一個InvocationHandler實現類的物件(可以使用匿名內部類的寫法)也可以定義一個
InvocationHandler
實現類
②當通過代理類呼叫方法時,如何通過代理類呼叫被代理類同名的方法
在動態代理中我們不再需要再手動的建立代理類,我們只需要編寫一個動態代理類就可以了。真正的代理物件由JDK再執行時為我們動態的來建立。
第一步:編寫一個動態代理類(二選一即可)
InvocationHandler實現類,
匿名內部類寫法
public class ProxyFactory { /** *呼叫此方法返回一個代理類物件 * @param obj 被代理類物件 * @return */ public static Object getProxyInstance (final Object obj) { // Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
//匿名內部類寫法
Object proxy = Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() { /** * 當我們通過代理類呼叫被代理類的a方法時,會自動的呼叫如下方法invoke */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = method.invoke(obj, args); return result; } }); return proxy; } }
定義一個
InvocationHandler
實現類
public class ProxyFactory { /** *呼叫此方法返回一個代理類物件 * @param obj 被代理類物件 * @return */ public static Object getProxyInstance (final Object obj) { MyInvocationHandler myInvocationHandler=new MyInvocationHandler(obj); Object proxy = Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),myInvocationHandler ); return proxy; } } class MyInvocationHandler implements InvocationHandler{ private Object object; public MyInvocationHandler(Object object) { this.object = object; } /** * 當我們通過代理類呼叫被代理類的a方法時,會自動的呼叫如下方法invoke */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = method.invoke(object, args); return result; } }
第二步:編寫測試類
@Test public void test1() { NikeClothFactory nikeClothFactory=new NikeClothFactory(); ClothFactory proxyInstance =(ClothFactory)ProxyFactory.getProxyInstance(nikeClothFactory); proxyInstance.produceCloth(); }
結果
Nike正在生產衣服
動態代理總結:
1)寫起來很難
2)Jdk預設的動態代理,如果目標物件沒有實現任何介面,是無法為他建立代理物件的
(代理物件和被代理物件唯一能產生的關聯就是實現了同一個介面)