1. 程式人生 > >代理模式是什麼?如何在 C# 中實現代理模式

代理模式是什麼?如何在 C# 中實現代理模式

代理模式 並不是日常開發工作中常常用到的一種設計模式,也是一種不易被理解的一種設計模式。但是它會廣泛的應用在系統框架、業務框架中。

定義

它的 定義 就如其它同大部分 設計模式 的定義類似,即不通俗也不易懂,而且隨便百度一下就能找到 : 為其他物件提供一種代理,以控制對這個物件的訪問。代理物件在客戶端和目標物件之間起到中介的作用。

每個字都認識,連在一起就看不懂了 by. 某個攻城獅

我們一個詞一個詞看就明白了。

其他物件

所謂的 其它,其實就是你係統中 任意 一個型別,可以是 UserServiceOrderRepositoryDataDeletedEventListener、等等。

控制對這個物件的訪問

訪問 其實就是呼叫這個物件上的方法、訪問它的屬性、設定它的屬性等等,比如

User user = UserService.GetUserById(1); // 訪問了 GetUserById 方法
int id = user.Id; // 訪問了 Id 屬性
Order order = OrderRepository.SelectByUserId(id); // 訪問了 SelectByUserId 方法

控制訪問 ,控制 的本質是包裝,外部不再直接使用 其他物件 ,而是使用 代理 ,再由代理來訪問 其它物件。我們可以使用一個已有的 List<T> 實現一個 IList<T>

,並在使用 Add 方法時,列印一行日誌。

public class LogList<T> : IList<T>
{
  // other code here..
  
  private IList<T> raw; // 這個就 "其它物件"
  
  public EventList(IList<T> raw)
  {
      this.raw = raw; // 通過建構函式,這可以讓 EventList 控制對 IList<T> 的訪問。
  }
  
  public void Add(T value)
  {
      this.raw.Add(value);
      Console.WriteLine(value);
  }
}

上面就是一個簡單的代理模式的例子:
IList<T> 提供一種 LogList<T> ,以控制對 IList<T> 的訪問。

實現

簡單實現

上面 LogList<T> 就是一種簡單的實現。

但是你無法對這個類做外部擴充套件,所有的邏輯都在型別的內部被固定了。

於是我們可以使用下面的方法建立一個可以靈活擴充套件的 ListProxy<T>

public interface IListInterruption<T>
{
    // other codes
    
    // 執行 IList.Add 時會進入的方法
    void OnAdding(IList<T> list, T addingValue);

    // 執行完 IList.Add 時會進入的方法
    void OnAdded(IList<T> list, T addedValue);

    // other codes

}

// 列表代理類
// 允許外部提供 IListInterruption 來豐富 ListProxy<T> 的邏輯。
public class ListProxy<T> : IList<T>
{
    private readonly IList<T> raw;
    private readonly List<IListInterruption> interruptions;

    public ListProxy(IList<T> raw)
    {
        this.interruptions = new List<IListInterruption>();
        this.raw = raw;
    }

    public void AddInterruption(IListInterruption interruption)
    {
        this.interruptions.Add(interruption);
    }

    public void Add(T value)
    {
        foreach(var item in this.interruptions)
            item.OnAdding(this.raw, value);

        this.raw.Add(value);
        
        foreach(var item in this.interruptions)
            item.OnAdded(this.raw, value);
    }
}

上面的程式碼實現一個較為靈活的 ProxyList<T>

首先看看 IListInterruption。通過實現 IListInterruption 介面,可以向 ProxyList 提供各種各樣的功能。

我們可以看一個簡單的功能

public class LogListInterruption<T> : IListInterruption<T>
{
    // other codes

    public void OnAdding(IList<T> list, T addingValue)
    {
        Console.WriteLie("Adding : {0}", addingValue);
    }

    // other codes
}

ProxyList 新增上述元件,就可以實現在 Add 前列印待新增的值的功能。

List<int> myList = new List<int>();
ProxyList<int> proxy = new ProxyList<int>(myList);
proxy.AddInterruption(new LogListInterruption<int>());
proxy.Add(1);
// >> Adding : 1

這種實現方式可以創建出針對某個型別的代理類,並通過外部給予的 IListInterruption 來豐富代理類功能。

但缺點是,當你無法為所有的型別都建立 ProxyInterruption

動態代理類

之前的方法中,我們在編寫階段就已經建立了代理類,被稱為靜態代理類。

這種方法無法將代理類運用在系統中任何一個我們可能需要的型別上。

於是,動態代理類 就來了。

動態代理類 依靠程式語言本身的特徵,讓程式在 執行時 建立型別,實現介面,新增功能等等。

在 C# 中可以通過兩種方式實現執行時建立型別的功能

  • CodeDom + 執行時編譯
  • Emit

CodeDom 可以用來生成 C# 程式碼,再利執行時編譯,會將C#程式碼編譯為記憶體中的程式集,最後通過反射訪問程式集中的成員。
這種方法的特點就是慢。。。。因為要生成語句,還要編譯,生成程式集,最後還要反射,都是大開銷,所以慢是可想而知的。

Emit 提供了利用 IL 命令在執行時建立型別、方法,並填充方法內的功能。
畢竟 C# 最終就是編譯成 IL 的,所以直接使用 IL 效能當然快無敵了。

這個方式的缺點只有一個 : 學習 IL 。這玩意可不是每個人都願意去學的。。。

於是,選擇一些已經利用 Emit 做好了動態代理類功能的第三方功能庫,成為了一個很好的選擇。

C# 大環境下,可以用來生成動態代理類的庫一般有兩個選擇 :

  • PostSharp
  • Caslte.DynamicProxy

其中 PostSharp 使用了更復雜的技術,不是使用 Emit,而且在編譯時,就將代理類所附加的功能放進了目標型別中,你可以通過 Attribute 向任意的方法新增額外的功能。

PostSharp 會在程式編譯時,把這些 Attribute 的功能直接編譯到方法內部。
這種在編譯時就把一切準備好的方法,讓 PostSharp 有著最高的執行效能。
但是又傻瓜、又好用的 PostSharp 只有一個缺點 ———— 收費。

Castle.DynamicProxy 是免費的,他是利用 Emit 在程式執行時建立代理類的。
使用 Castle.DynamicProxy 有兩個步驟:

  1. 編寫 Interceptor
  2. Interceptor 附加到某個型別或介面上,並得到一個會呼叫你的 Interceptor 的代理類例項

開發流程很像之前的 LogList 的例子。

相比較 PostSharp 那種一個 Attribute 就搞定一切的模式, Caslte.DynamicProxy 就沒有那麼方便了。

那麼一個顯而易見的問題就來了 :
能不能利用 Caslte.DynamicProxy 實現像 PostSharp 那樣利用 Attribute 建立代理類的功能呢?

Reface.AppStarter.Proxy

這是基於 Reface.AppStarter 開發的一個功能模組,
使用它,可以利用 Attribute 的方式輕鬆的建立代理類,並實現 AOP 的功能。

你所要做的,就是建立一個繼承於 ProxyAttribute 的特徵。

ProxyAttribute 中有三個方法需要重寫

  • OnExecuting ,被標記的方法執行時
  • OnExecuted ,被標記的方法執行後
  • OnExecuteError , 被標記的方法執行出現異常後

你可以編寫你的邏輯在這三個方法內,並將你的 Attribute 掛載到你需要的型別的方法上即可。

剩下的事情只有兩件

  • 向你的 AppModule 新增 ProxyAppModule
  • 為你需要建立代理的型別加上 [Component] 特徵

你已經完成了所有工作,
當你利用 Reface.AppStarter 的框架的 IOC / DI 容器建立你的型別時,實際得到的就是代理類,這些代理類會除錯你給予的 ProxyAttribute

關於 Reface.AppStarter.Proxy 的細節,會在以後的文章中進一步介紹。


相關連結

  • Reface.AppStarter.Proxy @ Nuget
  • Reface.AppStarter.Proxy @ Github
  • Reface.AppStarter.Proxy @ Gitee
  • Reface.AppStarter 框架初探