代理模式是為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用,其特征是代理類與委托類有同樣的介面。 動機: 在軟體設計中,使用代理模式的意圖也很多,比如因為安全原因需要屏蔽客戶端直接訪問真實對象,或 ...
代理模式是為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用,其特征是代理類與委托類有同樣的介面。
動機:
在軟體設計中,使用代理模式的意圖也很多,比如因為安全原因需要屏蔽客戶端直接訪問真實對象,或者在遠程調用中需要使用代理類處理遠程方法調用的技術細節 (如 RMI),也可能為了提升系統性能,通過控制來延遲對象的創建和實例化,直到真正需要使用該對象才進行創建和實例化。
由於一些對象創建和實例化需要占用大量系統資源,但我們並不能確定用戶一定會調用該對象,所以通過延遲對象實例化來減緩系統資源的消耗。例如文檔編輯器如word,我們可以在裡面插入鏈接、圖片等,但是並不是我們每次打開word時都有創建和實例化這些對象,特別是實例化圖片對象很消耗資源,並不需要實例化所有圖片。當我們在查看word時,只是看到其中的一部分,所以沒有必要實例化所以資源,當我們看下一頁時再實例化也不遲。
類型:結構類模式
類圖:
圖1 代理模式類圖
代理模式角色:
1) 主題介面:定義代理類和真實主題的公共對外方法,也是代理類代理真實主題的方法;
2) 真實主題:真正實現業務邏輯的類;
3) 代理類:用來代理和封裝真實主題;
4) Main:客戶端,使用代理類和主題介面完成一些工作。
優點:
- 對客戶端來說,隱藏了真實對象的細節及複雜性。
- 將代理對象與真正被調用的對象分離,在一定程度上降低了系統的耦合度。
- 在客戶端和目標對象之間起到一個中介作用,這樣可以起到保護目標對象的作用,也可以對目標對象調用之前進行其他操作。
- 遠程代理使得客戶端可以訪問在遠程機器上的對象,遠程機器可能具有更好的性能與處理速度,可以快速響應並處理客戶端請求。
- 虛擬代理通過使用一個小對象來代表一個大對象,可以減少系統資源的消耗,對系統進行優化並提高運行速度。
- 安全代理可以控制對真實對象的使用許可權。
缺點:
- 在客戶端和目標對象增加一個代理對象,會造成請求處理速度變慢。
- 增加了系統的複雜度。
適用場景
1) 遠程代理:也就是為一個對象在不同的地址空間提供局部代表,這樣可以隱藏一個對象存在於不同地址空間的事實。比如說 WebService,當我們在應用程式的項目中加入一個 Web 引用,引用一個 WebService,此時會在項目中聲稱一個 WebReference 的文件夾和一些文件,這個就是起代理作用的,這樣可以讓那個客戶端程式調用代理解決遠程訪問的問題。還有.NET的WCF的遠程代理。
2) 虛擬代理:是根據需要創建開銷很大的對象,通過它來存放實例化需要很長時間的真實對象。這樣就可以達到性能的最優化,比如打開一個網頁,這個網頁裡面包含了大量的文字和圖片,但我們可以很快看到文字,但是圖片卻是一張一張地下載後才能看到,那些未打開的圖片框,就是通過虛擬代理來替換了真實的圖片,此時代理存儲了真實圖片的路徑和尺寸。
3) 安全代理:用來控制真實對象訪問時的許可權。一般用於對象應該有不同的訪問許可權的時候。
4) 指針引用:是指當調用真實的對象時,代理處理另外一些事。比如計算真實對象的引用次數,這樣當該對象沒有引用時,可以自動釋放它,或當第一次引用一個持久對象時,將它裝入記憶體,或是在訪問一個實際對象前,檢查是否已經釋放它,以確保其他對象不能改變它。這些都是通過代理在訪問一個對象時附加一些內務處理。
5) 智能指引:當調用真實對象時,代理提供一些額外的操作。如將對象被操作的次數記錄起來等。
6) 延遲載入,用代理模式實現延遲載入的一個經典應用就在 Hibernate 框架裡面。當 Hibernate 載入實體 bean 時,並不會一次性將資料庫所有的數據都裝載。預設情況下,它會採取延遲載入的機制,以提高系統的性能。Hibernate 中的延遲載入主要分為屬性的延遲載入和關聯表的延時載入兩類。實現原理是使用代理攔截原有的 getter 方法,在真正使用對象數據時才去資料庫或者其他第三方組件載入實際的數據,從而提升系統性能。
7) 緩衝代理:為某一個目標操作提供臨時的存儲空間,以便更多客戶端共用此結果。
8) 防火牆代理:保護目標不讓惡意用戶接近。
9) 同步化代理:使幾個用戶能同時使用一個對象而沒有衝突。
代理模式分類
1. 靜態代理(靜態定義代理類,自己靜態定義的代理類)
1)優點:
可以做到在不修改目標對象的功能前提下,對目標功能擴展。
2)缺點:
因為代理對象需要與目標對象實現一樣的介面,所以會有很多代理類,類太多。同時,一旦介面增加方法,目標對象與代理對象都要維護。
3)靜態代理模式角色:
- 抽象角色:指代理角色和真實角色對外提供的公共方法,一般為一個介面。
- 真實角色:需要實現抽象角色介面,定義了真實角色所要實現的業務邏輯,以便供代理角色調用,真正的業務邏輯在此。
- 代理角色:需要實現抽象角色介面,是真實角色的代理,通過真實角色的業務邏輯方法來實現抽象方法,並可以附加自己的操作。將統一的流程式控制制都放到代理角色中處理。
代碼實現:
JAVA
//介面 public interface IClient { void appeal();//談判 } /** * 介面實現 * 目標對象 */ public class Client implements IClient { public void appeal() { System.out.println("委托人談判"); } } /** * 代理對象,靜態代理 */ public class ClientProxy implements IClient { private Client client; public ClientProxy(Client client) { this.client=client; } public void appeal() { System.out.println("代理談判開始!"); client.appeal(); System.out.println("代理談判結束!"); } } public class AppTest { public static void main(String[] args) { // TODO Auto-generated method stub //目標對象 Client target = new Client(); //代理對象,把目標對象傳給代理對象,建立代理關係 ClientProxy proxy = new ClientProxy(target); proxy.appeal();//執行的是代理的方法 } }
輸出結果:
C#
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace proxyDemo { // 客戶端調用 class Program { static void Main(string[] args) { // 創建一個代理對象併發出請求 Person proxy = new Friend(); proxy.Appeal(); Console.Read(); } } // 抽象主題角色 public abstract class Person { public abstract void Appeal(); } //真實主題角色:委托人 public class Client : Person { public override void Appeal() { Console.WriteLine("委托人談判中……"); } } // 代理角色:律師 public class Friend : Person { // 引用真實主題實例 Client client; public override void Appeal() { Console.WriteLine("律師代理談判……"); if (client == null) { client = new Client(); } this.PreAppeal(); // 調用真實主題方法 client.Appeal(); this.PostAppeal(); } public void PreAppeal() { Console.WriteLine("談判開始~"); } public void PostAppeal() { Console.WriteLine("談判結束!"); } } }View Code
輸出結果:
2. 動態代理(通過程式動態生成代理類,該代理類不是自己定義的。而是由程式自動生成)
特點:
- 代理對象不需要實現介面
- 代理對象的生成,是利用JDK的API,動態的在記憶體中構建代理對象(需要我們指定創建代理對象/目標對象實現的介面的類型)
- 動態代理也叫做:JDK代理,介面代理
基本原理:
通過掃描被代理類的所有 public 方法,並且自動生成一個從被代理類繼承的類,然後在這個生成的類中 override 這些 public 方法。這裡說的自動生成,並不是生成源代碼,而是直接使用中間語言(Intermedial Language)直接在記憶體中生成。這樣在速度上和源碼編譯而成的中間語言相近,可以利用 Reflection Emit API 來直接生成中間語言。
代碼實現:
JAVA
public interface IClient { void appeal();//談判 } /** * 介面實現 * 目標對象 */ public class Client implements IClient { public void appeal() { System.out.println("委托人談判"); } } import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * 處理器 */ public class ClientHandler implements InvocationHandler{ private IClient client;//真實角色 /** * 所有的流程式控制制都在invoke方法中 * proxy:代理類 * method:正在調用的方法 * args:方法的參數 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object object = null; System.out.println("律師動態調用之前的處理....."); if (method.getName().equals("appeal")) { object = method.invoke(client, args);//激活調用的方法 } System.out.println("律師動態調用之後的處理....."); return object; } //通過構造器來初始化真實角色 public ClientHandler(IClient client) { super(); this.client = client; } } import java.lang.reflect.Proxy; public class MainTest { public static void main(String[] args) { // TODO Auto-generated method stub // 目標對象 IClient target = new Client(); // 【原始的類型 class cn.itcast.b_dynamic.UserDao】 System.out.println(target.getClass()); //處理器 ClientHandler handler = new ClientHandler(target); // 給目標對象,創建代理對象 IClient proxy = (IClient) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{IClient.class}, handler); // class $Proxy0 記憶體中動態生成的代理對象 System.out.println(proxy.getClass()); // 執行方法 【代理對象】 proxy.appeal(); } }View Code
輸出結果: