1. 程式人生 > 實用技巧 >結構型模式之代理模式

結構型模式之代理模式

目錄

在有些情況下,一個客戶不能或者不想直接訪問另一個物件,這時需要找一箇中介幫忙完成某項任務,這個中介就是代理物件。

定義與特點

由於某些原因需要給某物件提供一個代理以控制對該物件的訪問。這時,訪問物件不適合或者不能直接引用目標物件,代理物件作為訪問物件和目標物件之間的中介

主要優點有:

  • 代理模式在客戶端與目標物件之間起到一箇中介作用和保護目標物件的作用
  • 代理物件可以擴充套件目標物件的功能
  • 代理模式能將客戶端與目標物件分離,在一定程度上降低了系統的耦合度

主要缺點有:

  • 在客戶端和目標物件之間增加一個代理物件,會造成請求處理速度變慢
  • 增加了系統的複雜度

結構與實現

代理模式的結構比較簡單,主要是通過定義一個繼承抽象主題的代理來包含真實主題,從而實現對真實主題的訪問。

模式的結構

代理模式的主要角色如下:

  • 抽象主題(Subject)類:通過介面或抽象類(推薦使用介面)宣告真實主題和代理物件實現的業務方法
  • 真實主題(Real Subject)類:實現了抽象主題中的具體業務,是代理物件所代表的真實物件,是最終要引用的物件
  • 代理(Proxy)類:提供了與真實主題相同的介面,其內部含有對真實主題的引用,它可以訪問、控制或擴充套件真實主題的功能

其結構圖如圖所示:

模式的實現

代理模式的實現程式碼如下:

//訪問類
class Program
{
    static void Main(string[] args)
    {
        //代理模式        
        Proxy proxy=new Proxy();
        proxy.Request();       
        Console.ReadKey();
    }
}

//抽象主題
public interface ISubject
{
    void Request();
}

//真實主題
public class RealSubject :ISubject
{
    public void Request()
    {
        Console.WriteLine("訪問真實主題方法...");
    }
}

//代理
public class Proxy : ISubject
{
    private RealSubject realSubject;
    public void Request()
    {
        if (realSubject==null)
        {
            realSubject=new RealSubject();
        }
        PreRequest();
        realSubject.Request();
        PostRequest();
    }
    public void PreRequest()
    {
        Console.WriteLine("訪問真實主題之前的預處理。");
    }
    public void PostRequest()
    {
        Console.WriteLine("訪問真實主題之後的後續處理。");
    }
}

程式執行的結果如下:

訪問真實主題之前的預處理。
訪問真實主題方法...
訪問真實主題之後的後續處理。

應用場景

前面分析了代理模式的結構與特點,現在來分析以下的應用場景:

  • 遠端代理(Remote Proxy):這種方式通常是為了隱藏目標物件存在於不同地址空間的事實,方便客戶端訪問。
    例如,使用者訪問網盤的虛擬硬碟時實際訪問的是網盤空間。
  • 虛擬代理(Virtual Proxy):這種方式通常用於要建立的目標物件開銷很大時。
    例如,下載一幅很大的影象需要很長時間,這時可以先用小比例的虛擬代理替換真實的物件,消除使用者對伺服器慢的感覺。
  • 保護代理(Protection Proxy):這種方式通常用於控制不同種類客戶對真實物件的訪問許可權。
  • 智慧指引(Smart Reference):主要用於呼叫目標物件時,代理附加一些額外的處理功能。

智慧指引的典型用途包括:

  • 增加計算真實物件的引用次數的功能,這樣當該物件沒有被引用時,就可以自動釋放它;
  • 當第一次引用一個持久物件時,將它裝入記憶體。
  • 在訪問一個實際物件前,檢查是否已經鎖定了它,以確保其他物件不能改變它。

擴充套件:動態代理模式

在前面介紹的代理模式中,代理類中包含了對真實主題的引用,這種方式存在兩個缺點:

  • 真實主題與代理主題一一對應,增加真實主題也要增加代理
  • 設計代理以前真實主題必須事先存在,不太靈活。

採用動態代理模式可以解決以上問題(如 SpringAOP),C#中可以使用RealProxy實現動態代理,有兩種方法:
第一種:只使用RealProxy,不能代理帶out引數的方法(可能是我沒找到),程式碼如下:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("***\r\n Begin program - logging with decorator\r\n");
        IRepository<Customer> customerRepository =RepositoryFactory.Create<Customer>();
        var customer = new Customer()
        {
            Id = 1,
            Name = "Customer 1",
            Address = "Address 1"
        };
        customerRepository.Add(customer);
        customerRepository.Update(customer);
        customerRepository.Delete(customer);
        Console.WriteLine("\r\nEnd program - logging with decorator\r\n***");
        Console.ReadLine();
    }
}
//客戶類
public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
}

//儲存庫介面
public interface IRepository<T>
{
    void Add(T entity);
    void Delete(T entity);
    void Update(T entity);
    IEnumerable<T> GetAll();
    T GetById(int id);
}

//真實儲存庫
public class Repository<T> : IRepository<T>
{
    public void Add(T entity)
    {
        Console.WriteLine("Adding {0}", entity);
    }
    public void Delete(T entity)
    {
        Console.WriteLine("Deleting {0}", entity);
    }
    public void Update(T entity)
    {
        Console.WriteLine("Updating {0}", entity);
    }
    public IEnumerable<T> GetAll()
    {
        Console.WriteLine("Getting entities");
        return null;
    }
    public T GetById(int id)
    {
        Console.WriteLine("Getting entity {0}", id);
        return default(T);
    }
} 

//動態代理
class DynamicProxy<T> : RealProxy
{
    private readonly T _decorated;
    public DynamicProxy(T decorated) : base(typeof(T))
    {
        _decorated = decorated;
    }
    private void Log(string msg, object arg = null)
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine(msg, arg);
        Console.ResetColor();
    }
    public override IMessage Invoke(IMessage msg)
    {
        var methodCall = msg as IMethodCallMessage;
        var methodInfo = methodCall.MethodBase as MethodInfo;
        Log("In Dynamic Proxy - Before executing '{0}'",methodCall.MethodName);
        try
        {
            var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
            Log("In Dynamic Proxy - After executing '{0}' ",methodCall.MethodName);
            return new ReturnMessage(result, null, 0,methodCall.LogicalCallContext, methodCall);
        }
        catch (Exception e)
        {
            Log(string.Format("In Dynamic Proxy- Exception {0} executing '{1}'", e),methodCall.MethodName);
            return new ReturnMessage(e, methodCall);
        }
    }
}

//存倉庫過程,自動執行代理
public class RepositoryFactory
{
    public static IRepository<T> Create<T>()
    {
        var repository = new Repository<T>();
        var dynamicProxy = new DynamicProxy<IRepository<T>>(repository);
        return dynamicProxy.GetTransparentProxy() as IRepository<T>;
    }
}    

第二種:使用RealProxy、MarshalByRefObject,可以代理帶out引數的方法,程式碼如下:

//訪問類
public class Program
{
    static void Main(string[] args)
    {
        //動態代理模式        
        Proxy<ISubject> proxy = new Proxy<ISubject>(new RealSubject());
        ISubject subject = (ISubject)proxy.GetTransparentProxy();
        int arg = 0;
        subject.Request(out arg); 
        Console.WriteLine(arg);
        Console.ReadKey();
    }
}
//代理類
public class Proxy<T> : RealProxy where T: class
{    
    MarshalByRefObject myMarshalByRefObject;
    public Proxy(MarshalByRefObject realT) : base(typeof(T))
    {        
        myMarshalByRefObject = realT;
    }
    public override IMessage Invoke(IMessage myMessage)
    {        
        IMethodCallMessage myCallMessage = (IMethodCallMessage)myMessage;
        Console.WriteLine("動態代理方法中:執行前");
        IMethodReturnMessage myIMethodReturnMessage = RemotingServices.ExecuteMessage(myMarshalByRefObject, myCallMessage);
        Console.WriteLine("動態代理方法中:執行後");
        return myIMethodReturnMessage;
    }
}

//抽象主題
public interface ISubject
{
    void Request(out int arg);
}

//真實主題
public class RealSubject : MarshalByRefObject,ISubject
{
    public void Request(out int arg)
    {
        arg = 1;
        Console.WriteLine("訪問真實主題方法...");       
    }
}

參考資料

C#中動態代理與泛型函式——CSDN
面向方面的程式設計-使用 RealProxy 類進行面向方面的程式設計——MSDN