1. 程式人生 > >設計模式之代理模式(Proxy)(2)

設計模式之代理模式(Proxy)(2)

技術分享 strong 流程 imp pla 遠程調用 height bst 速度

代理模式是為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用,其特征是代理類與委托類有同樣的接口。

動機:

在軟件設計中,使用代理模式的意圖也很多,比如因為安全原因需要屏蔽客戶端直接訪問真實對象,或者在遠程調用中需要使用代理類處理遠程方法調用的技術細節 (如 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

輸出結果:

技術分享圖片

設計模式之代理模式(Proxy)(2)