標籤:

結構型模式之代理模式

結構型模式之代理模式(靜態代理)

  • 由於某些原因,客戶端不想或不能直接訪問一個對象,此時可以通過一個稱之為「代理」的第三者來實現間接訪問,該方案對應的設計模式被稱為代理模式。
  • 代理其實是實現簡介訪問的媒介,當然在代理類中還可以在執行代理操作之前,之後,之中,環繞等執行相關動作。Spring 中面向切面編程就是這個原理
  • 代理模式是一種應用很廣泛的結構型設計模式,而且變化形式非常多,常見的代理形式包括遠程代理、保護代理、虛擬代理、緩衝代理、智能引用代理等,後面將學習這些不同的代理形式
  • 當使用代理類的時候, 真實類中的信息對用戶來說是透明的(不可見的)
  • 主要就是用於對象的間接訪問提供了一個方案,可以對對象的訪問進行控制

定義

  1. Subject(抽象主題角色):它聲明了真實主題和代理主題的共同介面,這樣一來在任何使用真實主題的地方都可以使用代理主題,客戶端通常需要針對抽象主題角色進行編程。
  2. Proxy(代理主題角色):它包含了對真實主題的引用,從而可以在任何時候操作真實主題對象;在代理主題角色中提供一個與真實主題角色相同的介面,以便在任何時候都可以替代真實主題;代理主題角色還可以控制對真實主題的使用,負責在需要的時候創建和刪除真實主題對象,並對真實主題對象的使用加以約束。通常,在代理主題角色中,客戶端在調用所引用的真實主題操作之前或之後還需要執行其他操作,而不僅僅是單純調用真實主題對象中的操作。
  3. RealSubject(真實主題角色):它定義了代理角色所代表的真實對象,在真實主題角色中實現了真實的業務操作,客戶端可以通過代理主題角色間接調用真實主題角色中定義的操作。

實例

第一個例子

  • 需求: 我們知道mac筆記本是在美國生產的,那麼如果中國供銷商想要賣mac筆記本,那麼必須從美國供銷商那裡先進貨,然後中國的顧客才可以在中國供銷商買mac。這裡的中國供銷商就相當於代理,美國供銷商就相當於真實主題角色
  • Mac筆記本抽象介面(相當於其中的抽象主題)

/* * 蘋果筆記本的介面,其中有一個方法實現了買筆記本的動作 */public interface MacBook { public void buy(); //購買筆記本的行為}

  • 美國供銷商(相當於這裡RealSubject)

/* * 美國的筆記本,實現了MacBook介面,表示在美國買筆記本 */public class USAMac implements MacBook { @Override public void buy() { System.out.println("在美國買筆記本"); }}

  • 中國供銷商(相當於這裡的代理角色)

我們可以看到我們在使用代理模式的時候可以在之前和之後進行操作/* * 中國的筆記本,實現了MacBook 表示在中國買筆記本 * 但是中國想要買到蘋果筆記本,那麼還是需要先從美國進貨,因此中國只是一個中間的代理作用而已 * 當然代理的最大作用就是在代理之前、之後、之中執行相關的操作,這就是面向切面編程的原理 */public class ChinaMac implements MacBook { private MacBook mcBook=new USAMac(); //創建USAMac的對象 /** * 在購買之前執行的操作 */ public void preBuy(){ System.out.println("購買之前執行的操作"); } /** * 在購買之後執行的操作 */ public void afterBuy(){ System.out.println("購買之後執行的操作"); } @Override public void buy() { this.preBuy(); //之前執行的操作 mcBook.buy(); //在美國買筆記本 System.out.println("在中國買筆記本"); this.afterBuy(); //之後執行的操作 }}

  • 測試類
    • 我們在使用的時候直接使用代理類即可,我們根本不知道在真實類的使用,完全是代理類為我們提供了

public class Client { public static void main(String[] args) { MacBook macBook=new ChinaMac(); //創建ChinaMac對象,在中國買筆記本 macBook.buy(); //直接在中國買筆記本 }}

第二個例子

  • 我們登錄一個網站的伺服器端的驗證步驟:
    • 讀取用戶名和密碼
    • 驗證用戶名和密碼
    • 記錄到日誌中
  • 這裡的驗證密碼和記錄到日誌中可以在代理類中實現,在用戶執行操作之前需要讀取用戶名和密碼,並且驗證,在操作之後需要將用戶的一些操作記錄到日誌中。其實這裡的真實用戶需要做的只是執行自己的操作,而驗證和記錄都是交給代理類實現的。

實現

  • 用戶介面(User)

/* * 用戶的抽象類 */public interface User { public void DoAction(); //執行動作}

  • 真實的用戶類(實現了用戶介面)
    • 主要的做的就是執行自己的操作

public class RealUser implements User { public String name; public String password; public RealUser(String name, String password) { this.name = name; this.password = password; } public RealUser() {} /* * 執行一些操作 */ @Override public void DoAction() { System.out.println("開始執行操作......"); }}

  • 代理類(實現了User介面)
    • 在執行操作之前驗證密碼和用戶名是否正確
    • 在執行操作之後記錄到日誌中
    • 實際上這裡就是面向切面編程

public class ProxUser implements User { private RealUser user; // 真實用戶的對象 /** * 創建對象 * @param name 姓名 * @param password 密碼 */ public ProxUser(String name, String password) { user = new RealUser(name, password); } @Override public void DoAction() { //驗證用戶名和密碼 if (Validate()) { user.DoAction(); //調用真實用戶的DoAction方法執行相關操作 logger(); //調用日誌記錄信息 } else { System.out.println("請重新登錄......."); } } /* * 驗證用戶的用戶名和密碼 */ public Boolean Validate() { if ("陳加兵".equals(user.name) && "123456".equals(user.password)) { return true; } return false; } /** * 添加日誌記錄信息 */ public void logger() { System.out.println(user.name + "登錄成功......"); }}

  • 測試類
    • 實際上執行了驗證用戶名和密碼,記錄日誌的操作,但是對於客戶端來說只能看到自己執行的操作

public class Client { public static void main(String[] args) { ProxUser proxUser=new ProxUser("陳加兵", "123456"); //創建代理對象 proxUser.DoAction(); //執行操作,實際執行了驗證信息,doaction(),日誌記錄這個三個動作 }}

缺點

  • 如果增加一個介面就需要增加一個代理類,如果是要增加很多,那麼就要增加很多代理類,代碼將會重複

解決方法

  • 下面我們將會講解到動態代理,僅僅需要一個代理類即可

結構型模式之動態代理模式

  • 前面我們說的代理模式其實是屬於靜態代理模式,就是說在程序執行之前已經寫好了代理類,但是缺點也是說過,必須為每個介面都實現一個代理類,如果有多個介面需要代理,那麼代碼肯定是要重複的,因此就需要動態代理了。
  • 動態代理可以實現多個介面共用一個代理類,只需要改變初始化的參數即可,可以省去很多的重複的代碼。
  • JDK的動態代理需要一個類一個介面,分別為Proxy和InvocationHandler
  • 主要原理就是利用了反射的原理

InvocationHandler

  • 這個是代理類必須實現的介面,其中有一個方法public Object invoke(Object proxy,Method method,Object[] args)
    • Object proxy:指被代理的對象。
    • Method method:要調用的方法
    • Object[] args:方法調用時所需要的參數

Proxy

  • Proxy類是專門完成代理的操作類,可以通過此類為一個或多個介面動態地生成實現類,此類提供了如下的操作方法:

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
    • ClassLoader loader:類載入器
    • Class<?>[] interfaces:得到全部的介面
    • InvocationHandler h:得到InvocationHandler介面的子類實例

實例

  • 肯德基的介面

/* * 肯德基的介面,其中一個eat方法 */public interface IKFC { public void eat();}

  • 肯德基的實現類(RealSubject)

/* * IKFC的實現類 */public class KFC implements IKFC { @Override public void eat() { System.out.println("我在肯德基吃了飯......"); }}

  • 蘋果筆記本的介面

/* * 蘋果筆記本的介面 */public interface MacBook { public void buy();}

  • 美國供銷商的類(RealSubject)

/* * 美國筆記本的類,實現了MacBook介面 */public class USAMacBook implements MacBook { @Override public void buy() { System.out.println("在美國買了一個蘋果電腦......"); }}

  • 動態代理的類(實現了InvocationHandler介面)

import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;/** * 這個是代理類,實現了InvocationHandler介面 * */public class ProxyHandler implements InvocationHandler { private Object Realobject; //被代理的對象 //構造方法,用來初始化被代理的對象 public ProxyHandler(Object obj){ this.Realobject=obj; //初始化真實類的對象 } /** * @param proxy 表示被代理的對象的,就是真實類的對象 * @param method 表示要調用真實類的方法 * @param args 表示方法調用的時候所需要的參數 * @return 方法調用之後的返回值 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { prefunction(); //執行之前調用的方法 Object res=method.invoke(Realobject, args); //Method類中的執行方法的函數,在反射中常用 afterFunction(); //執行之後調用的方法 return res; } /** * 執行方法之前調用的方法 */ public void prefunction(){ System.out.println("執行方法之前......"); } /** * 執行方法之後調用的方法 */ public void afterFunction(){ System.out.println("執行方法之後......"); }}

  • 測試類

import java.lang.reflect.Proxy;import com.sun.org.apache.bcel.internal.generic.NEW;import com.sun.org.apache.bcel.internal.util.Class2HTML;public class Client { public static void main(String[] args) { Class[] cls1={IKFC.class}; //第一個代理的所有介面數組,直接用介面的反射即可 Class[] cls2=USAMacBook.class.getInterfaces(); //直接具體的實現類的反射調用getInterfaces即可返回所有的介面數組 // 返回KFC的代理對象 IKFC kfc = (IKFC) Proxy.newProxyInstance(Client.class.getClassLoader(), cls1, new ProxyHandler(new KFC())); kfc.eat(); //執行方法 MacBook macBook = (MacBook) Proxy.newProxyInstance(Client.class.getClassLoader(), cls2, new ProxyHandler( new USAMacBook())); macBook.buy(); //執行方法 }}

總結

  • 動態代理的好處
    • 即使有多個介面,也僅僅只有一個動態代理類

推薦閱讀:

設計模式之「Decorator」註疏#02
音頻節目第三期——設計模式
創建型模式之建造者模式
深入理解MVC
創建型模式之原型模式

TAG:設計模式 |