1. 程式人生 > 其它 >設計模式之【代理模式】

設計模式之【代理模式】

設計原則是指導我們程式碼設計的一些經驗總結,也就是“心法”;面向物件就是我們的“武器”;設計模式就是“招式”。

以心法為基礎,以武器運用招式應對複雜的程式設計問題。

我:妹啊,怎麼我看你最近都很忙的樣子?不是換了個輕鬆點的工作嘛?

表妹:是啊,工作是輕鬆點了,但是上下班太遠了,想買輛二手車代步,所以我現在在做功課。

我:我看看...哇,還挺認真的,花了不少時間吧?

表妹:是啊,以前對這方面瞭解比較少,所以現在要做足功課才敢去買。

我:我有個同學是做這方面代理的,我讓他幫你搞定。

表妹:哇,這樣我就省事多啦~

你看,這不就是我們設計模式中的【代理模式】嘛?


為什麼要用代理模式?

  • 中介隔離作用

    在一些情況下,一個客戶類不想或者不能直接引用一個委託物件,而代理類物件可以在客戶類和委託物件之間起到中介作用,其特徵是代理類和委託類實現相同的介面。

  • 不違背開閉原則的前提下,增加功能

    代理類除了是客戶類和委託類的中介外,我們還可以通過代理類增加額外的功能來擴充套件委託類的功能,這樣做我們只需要修改代理類而不需要再修改委託類,符合程式碼設計的開閉原則。代理類主要負責為委託類預處理訊息,過濾訊息、把訊息轉發給委託類,以及事後對返回結果的處理等。

    代理類並不真正實現服務,而是通過呼叫委託類的相關方法,來提供特定的服務。真正的業務功能還是由委託類來實現,但是可以在業務功能執行的前後加入一些公關的服務。例如加入快取、日誌等功能,我們就可以使用代理類來完成,而沒必要修改已經封裝好的委託類。

按照代理建立的日期來進行分類的話,可以分為靜態代理和動態代理。

靜態代理:由程式設計師建立或特定工具自動生成原始碼,再對其進行編譯。在編譯執行之前,代理類.class檔案就已經被建立,代理類和委託類的關係在執行前就確定。

動態代理:動態代理類的原始碼是在程式執行期間由JVM根據反射等機制動態的生成,所以不存在代理類的位元組碼檔案。代理類和委託類的關係是在程式執行時確定的。

靜態代理是如何實現的?

代理實現的前提是,如何能夠獲取到委託類的呼叫?答案是有組合和繼承兩種方式。

組合:與委託類實現相同的介面,然後向代理類傳入委託類的引用從而呼叫到目標函式。

繼承:繼承委託類,重寫目標函式,然後通過super呼叫到目標函式。

我們先來看看用組合方式實現的。就舉表妹買二手車的例子來說。

第一步:建立買車介面:

1 public interface IBuyCar {
2     void buyCar() 
3 }

第二步:表妹要買車,實現買車的介面

1 public class BiaoMeiBuyCar implements IBuyCar {
2     @Override 
3     public void buyCar() {
4         System.out.println("交易二手車");
5     }
6 }

第三步:找代理來幫表妹完成這件事

 1 public class BiaoMeiBuyCarProxy implements IBuyCar {
 2     private IBuyCar buyCar;
 3     
 4     public BiaoMeiBuyCarProxy (final IBuyCar buyCar) {
 5         this.buyCar = buyCar;
 6     }
 7     
 8     @Override
 9     public void buyCar() {
10         // 代理除了幫忙交易外,還幫忙辦理前後手續
11         System.out.println("交易前手續辦理");   
12         buyCar.buyCar();  // 交易
13         System.out.println("交易後手續辦理");
14     }
15 }
1 IBuyCar buyCar = new BiaoMeiBuyCarProxy (new BiaoMeiBuyCar());

因為委託類和代理類實現相同的介面,是基於介面而非實現程式設計,所以,將BiaoMeiBuyCar類物件替換為BiaoMeiBuyCarProxy類物件,不需要改動太多程式碼。

大家發現沒有,基於組合方式的靜態代理模式跟裝飾器模式很像。

是的,對於裝飾器模式來說,裝飾者和被裝飾者都實現一個介面;對代理模式來說,代理類和委託類也都實現同一個介面。不論我們使用哪一種模式,都可以很容易地在真實物件的方法前面或後面加上自定義的方法。

這裡我們先簡單看一下兩者的區別,另外一篇我們再仔細分析這兩種設計模式的區別哈。

代理模式注重的是對物件的某一功能的流程把控和輔助,它可以控制物件做某些事,重點是為了借用物件的功能完成某一流程,而非物件功能如何。

裝飾器模式注重的是對物件功能的擴充套件,不關心外界如何呼叫,只注重對物件功能加強,裝飾後還是物件本身。

但是,如果委託類並沒有定義介面,並且委託類程式碼並不是我們開發維護的(比如,它來自一個第三方的類庫),我們也沒辦法直接修改原始類,給它重新定義一個介面。在這種情況下,我們該如何實現靜態代理模式呢?

對於這種情況,我們一般採用繼承的方式。讓代理類繼承委託類,然後擴充套件附加功能。

 1 public class BiaoMeiBuyCar {
 2     public void buyCar() {
 3         System.out.println("交易二手車");
 4     }
 5 }
 6  7 public class BiaoMeiBuyCarProxy extends BiaoMeiBuyCar {
 8     public void buyCar() {
 9         // 代理除了幫忙交易外,還替表妹辦理好前後手續
10         System.out.println("交易前手續辦理");
11         super.buyCar();   // 交易
12         System.out.println("交易後手續辦理");
13     }
14 }
15 16 public class Demo {
17     public static void main(String[] args) {
18         // 1、找代理物件
19         BiaoMeiBuyCar biaomeiBuyCarProxy = new BiaoMeiBuyCarProxy();
20         // 2、代理負責幫表妹搞定一切
21         biaomeiBuyCarProxy .buyCar();
22     }
23 }

你看,不管是基於組合方式還是基於繼承方式的靜態代理模式,一方面,都需要在代理類中,將原始類中的所有方法,都重新實現一遍,並且為每個方法都附加相似的程式碼邏輯。另一方面,如果要新增附加功能的類不止一個,我們就需要針對每個類都建立一個代理類。那必然會增加類的個數,增加了程式碼維護成本。而且,每個代理類中的程式碼都有點像模板式的“重複”程式碼,也增加了不必要的開發成本。

這時候,動態代理就派上用場了。

動態代理

在動態代理中,我們不再需要手動建立代理類,只需要編寫一個動態處理器就可以了。

JDK動態代理

真正的代理物件由JDK在執行時幫我們動態的建立。

第一步:建立買車介面:

1 public interface IBuyCar {
2     void buyCar() 
3 }

第二步:表妹要買車,實現買車的介面

1 public class BiaoMeiBuyCar implements IBuyCar {
2     @Override 
3     public void buyCar() {
4         System.out.println("交易二手車");
5     }
6 }

第三步:實現動態處理器

 1   import java.lang.reflect.InvocationHandler;
 2   import java.lang.reflect.Method;
 3  4   public class DynamicProxyHandler implements InvocationHandler {
 5       private Object object;
 6       
 7       public DynamicProxyHandler(final Object object) {
 8           this.object = object;
 9       }
10      
11      @Override 
12      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
13          System.out.println("交易前手續辦理");
14          Object result = method.invoke(object, args);
15          System.out.println("交易後手續辦理");
16          return result;
17      }
18  }
1 public class Demo {
2      public static void main(String[] args) {
3          IBuyCar biaomeiBuyCar = new BiaoMeiBuyCar();
4          IBuyCar buyCarProxy = (IBuyCar)Proxy.newProxyInstance(IBuyCar.class.getClassLoader(), new Class[]{IBuyCar.class}, new DynamicProxyHandler(biaomeiBuyCar));
5          buyCarProxy.buyCar();
6      }
7  }

但是,JDK實現動態代理需要實現類通過介面定義業務方法,對於沒有介面的類,就需要CGLib了。

CGLIB動態代理

CGLib採用了非常底層的位元組碼技術,其原理是通過位元組碼技術為一個類建立子類,並在子類中採用方法攔截的技術,攔截所有父類方法的呼叫,順勢織入橫切邏輯。

但因為採用的是繼承,所以不能對fianl修飾的類進行代理。

第一步:建立CGLib代理類。

 1 package wei.proxy.impl;
 2  3 import net.sf.cglib.proxy.Enhancer;
 4 import net.sf.cglib.proxy.MethodInterceptor;
 5 import net.sf.cglib.proxy.MethodProxy;
 6  7 import java.lang.reflect.Method;
 8  9 public class CglibProxy implements MethodInterceptor {
10     private Object target;
11     public Object getInstance(final Object target) {
12         this.target = target;
13         Enhancer enhancer = new Enhancer();
14         enhancer.setSuperclass(this.target.getClass());
15         enhancer.setCallback(this);
16         return enhancer.create();
17     }
18 19     public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
20         System.out.println("交易前手續辦理");
21         Object result = methodProxy.invokeSuper(object, args);
22         System.out.println("交易後手續辦理");
23         return result;
24     }
25 }
1 public class Demo {
2     public static void main(String[] args){
3         IBuyCar biaomeiBuyCar = new BiaoMeiBuyCar();
4         CglibProxy cglibProxy = new CglibProxy();
5         BiaoMeiBuyCar buyCarCglibProxy = (BiaoMeiBuyCar) cglibProxy.getInstance(biaomeiBuyCar);
6         buyCarCglibProxy.buyCar();
7     }
8 }

代理模式的優點

1、代理模式能將代理物件與真實被呼叫的目標物件隔離;

2、一定程度上降低了系統的耦合度,擴充套件性好;

3、可以起到保護目標物件的作用;

4、可以對目標物件的功能增強;

代理模式的缺點

1、代理模式會造成系統設計中類的數量增加;

2、在客戶端與目標物件之間增加一個代理物件,會造成請求處理速度變慢;

3、增加了系統的複雜度。

代理模式的應用場景

代理模式常用在業務系統中開發一些非功能性需求,比如:監控、統計、鑑權、限流、事務、冪等、日誌。

我們將這些附加功能與業務功能解耦,放到代理類統一處理,讓程式設計師只需要關注業務方面的開發。

除此之外,代理模式還可以用在RPC、快取等應用場景,

總結

代理模式其實就是在訪問物件時,引入一定程度的間接性,因為這種間接性,可以附加多種用途。

代理就是真實物件的代表。

參考資料

https://www.cnblogs.com/yanggb/p/10952843.html

極客時間專欄《設計模式之美》

https://www.cnblogs.com/daniels/p/8242592.html