1.簡介
定義:將某個物件中圍繞某個主題的一些列行為委托給一個代理物件去執行,代理物件將控制和管理對原有物件的訪問,呼叫者想要訪問目標物件,必須通過代理物件去間接訪問,代理物件在呼叫方和目標物件之間可以起到”中介“的作用,代理一詞本身,其實就可以很好發現的關鍵點,如果暫時無法理解晦澀的概念,那么在閱讀本文之前先通俗的理解:”就是找其他人代表你、協助你,去更好的幫你做事情“,
在現實生活中代理模式的場景其實無處不在,例如民用汽車消費場景就存在代理模式的影子,我們個人一般沒有權限直接從汽車生產商購買汽車的,并且需要專業人員為我們介紹汽車的引數,所以我們選擇從4S店作為購買汽車的途徑,那么在這個場景中,汽車4S店就屬于一個代理者,它代理了消費者從汽車生產商購買汽車的行為,不但提供給消費者便捷的購車渠道,還可以享受到售前的專業講解和售后維修保養服務,這些都是無法直接從汽車生產商獲得的,
2.應用場景介紹
在實際的開發場景中,我經常會遇到一種場景,簡單來說就是對現有的函式增加一些通用處理,例如,在訪問函式之前進行身份驗證、在資料操作之后進行日志記錄等,簡單粗暴的方式,就是將身份驗證和日志記錄的代碼直接添加在相應函式代碼最前面和代碼最后面,雖然這種方式解決了功能問題,但是在設計上存在一些弊端,
如果實作身份驗證或日志記錄的代碼邏輯存在隱患或產生變動,并且涉及使用的函式很多,那么這個改動量是比較大的,改動勢必會對系統產生風險,則需要為每個改動的方法及業務進行測驗作業(因為任何改動都會存在風險),這樣的場景反應了一個問題:函式中通用功能代碼和業務耦合在一起,通用功能的變化會引起這個函式的變化,以及不排除呼叫層對這個函式使用的變化,
為了減少函式中通用功能代碼和業務代碼之間的耦合性,這個時候我們就可以運用代理模式,簡單來說,我們在客戶端物件訪問原有業務函式之間增加一個代理物件,促使客戶端不能直接訪問業務函式,而只能通過代理物件間接訪問,

這樣一來,業務函式本身只專注于業務,與業務無關的擴展功能則轉交給代理物件,如果擴展功能發生變化,我們無需修改業務函式,而是修改代理物件,基于這種現象可以看出,代理模式很好的遵循了”開閉原則“,即類對擴展開放,對修改關閉,另外,代理物件除了提供給客戶端呼叫業務函式之外,還額外在業務函式執行之前和之后,提供身份驗證和日志記錄,
3.代理模式結構

3.1.Subject(抽象主題)
它是基于代理的“主題行為”抽象出的介面層,之所以稱為主題,是因為代理的行為會圍繞某個主題存在多個,比如資料庫操作這個主題,就存在“增刪改查”多個行為,
抽象主題是代理類和真實主題類都必須實作的介面,二者通過實作同一介面,代理類就可以在“真實主題類”使用的地方取代它,在呼叫層,客戶端物件就可以使用多型的形式,面向抽象主題介面編程,而抽象主題介面型別中實際的參考則是代理物件,代理物件中又包含了對“真實主題”物件的參考,從而促使呼叫層對“真實主題”物件的間接使用,
3.2.Proxy(代理類)
代理類很好理解,相當于“中介”,主要作用是控制物件的訪問,代理類中處理實作抽象主題以外,還需要包含對被代理物件的參考,之所以要參考被代理物件,那是因為代理行為具體的實作任然是被代理者提供的,代理類只是類似于擴展的性質,在代理行為的執行之前或之后,結合應用場景欄位外的附加操作,如權限控制、日志等,所以代理的行為還是建立在“被代理者”提供的行為基礎之上,
3.3.RealSubject(真實主題)
真實主題是真正做事的物件,它的訪問將由代理類進行控制,俗稱“被代理者”,抽象主題的定義往往就是根據“真實主題”的行為作為切入點抽象出來的,“真實主題”會承擔代理行為的具體實作邏輯,代理類中會參考“真實主題”物件對其進行呼叫,而在呼叫層不允許之間訪問該物件,而是通過代理物件間接的訪問它,
4.應用示例
接下來我們基于上文中的應用場景,以系統中常用的一個“用戶服務類”中的查詢方法作為我們實作代理模式的示例,我們將使用代理模式達到對“用戶服務類”的訪問控制,然后在代理類中在呼叫查詢方法的基礎上,在額外的增加身份驗證和日志記錄功能,該示例的代理模式結構如下:

在上面的類圖中,我們基于“用戶服務”中的行為作為主題抽象成了一個介面,介面中包含了我們需要代理的某個行為,即獲取用戶資訊,為了在編碼上代理類可以代替“用戶服務類”,故將它們都實作了“用戶服務介面”,這樣一來客戶端可以面向抽象編碼,將“代理類”和“用戶服務類”一致性看待,代理類中新增了Validate方法和Log方法,它們分別用于在“獲取用戶資訊”方法的基礎上,額外進行身份驗證和日志記錄,代理類中還參考了“用戶服務類”物件,它會重寫“GetUserList”方法,并在重寫方法中呼叫“用戶服務類”提供的“GetUserList”方法,然后再進行額外的功能附加,代碼示例如下:
1 /// <summary>
2 /// 用戶服務介面,代理模式中的“抽象主題類”
3 /// </summary>
4 public interface IUserService
5 {
6 List<string> GetUserList();
7 }
8
9
10 /// <summary>
11 /// 用戶服務類,代理模式中的“真實主題類”
12 /// </summary>
13 public class UserService : IUserService
14 {
15 public List<string> GetUserList()
16 {
17 Console.WriteLine("正在連接資料庫,查詢所有用戶資訊,,,");
18
19 List<string> userList = new List<string>
20 {
21 "蘇軾","李白","辛棄疾","岳飛","白居易"
22 };
23
24 return userList;
25 } // END GetUserList()
26
27 }
28
29 /// <summary>
30 /// 用戶服務代理類,代理模式中的“代理類”
31 /// </summary>
32 public class ProxyUserService : IUserService
33 {
34 private IUserService _userService = new UserService();
35
36 public List<string> GetUserList()
37 {
38 if (Validate()) //身份驗證
39 {
40 List<string> userList = _userService.GetUserList(); //呼叫真實主題物件的查詢方法
41 Log();//日志記錄
42 return userList;
43 }
44
45 return null;
46 } // END GetUserList()
47
48 public bool Validate()
49 {
50 //偽代碼,模擬獲取用戶資訊
51 string currentUserId = "張三";
52
53 if (currentUserId== "張三")
54 {
55 Console.WriteLine($"“{currentUserId}”用戶的權限認證成功!");
56 return true;
57 }
58 else
59 {
60 Console.WriteLine($"“{currentUserId}”用戶的權限認證失敗!");
61 return false;
62 }
63
64 } // END Validate()
65
66 public void Log()
67 {
68 //偽代碼,模擬獲取用戶資訊
69 string currentUserId = "張三";
70
71 Console.WriteLine($"用戶:“{currentUserId}與{DateTime.Now}查詢了用戶資訊,”");
72
73 }// END Log()
74
75 }
客戶端呼叫代碼:
1 //創建代理物件 2 IUserService proxyUserService = new ProxyUserService(); 3 4 //使用代理物件獲取用戶資訊 5 List<string> userList= proxyUserService.GetUserList(); 6 7 //輸出 8 Console.WriteLine("\r\n輸出用戶資訊:"); 9 foreach (var user in userList) 10 { 11 Console.WriteLine(user); 12 }
輸出結果:

5.動態代理
5.1.靜態代理的不足
代理模式中通過“代理物件”實作了對“目標物件”的控制,從而可以在“目標物件”原有的方法基礎上進行額外的擴展,并且這種擴展方式是可以在不修改原有目標物件代碼的基礎上實作,促使原有目標物件實作了開閉原則,
盡管如此,目前的代理模式仍有美中不足,由于我們代理類以及代理的行為都是預先定義好的,如果抽象主題中需要新增方法,也就是某個代理類要新增代理行為,那么代理類則必須要做出相應的實作,并且在實作的方法中,對于通用處理的功能,會在不同的方法中出現冗余,
例如本實體中的“用戶服務類”,在實際的專案中類似這種資料服務類,肯定不僅只有“查詢用戶”一種方法,必然會有“增刪改查”一系列的方法,如果要為其增加“增刪改”方法,那么代理類想要代理這些行為,則必須在重寫“抽象主題介面”的方法,并且對于通用附加功能(權限、日志等)的代碼會產生很多冗余,

除此之外,實際專案中如果存在大量的代理需求,那么我們可能會為不同型別、不同業務領域的服務類撰寫大量的代理類,在撰寫大量代理類后,你會發現代理類的結構都幾乎相同,都只是在代理行為的之前或之后做一些處理,那么這樣也會產生許多重復,基于這種背景下,為了尋找一種通用化的代理方案,就衍生出了一種動態代理模式,而以上我們示例中應用的模式反之為靜態代理,
對于靜態代理而言,代理類都是預先撰寫定義好的,這導致隨著代理需求的增加還需要新增相應的代理類,并且代理行為增加,代理類也需要不斷去實作相應的方法,“唯一不變的是變化本身”,我們不可能預知系統的所有代理需求,不可能預估系統中,哪些類、哪些方法需要被代理,
為了應對這種變化,我們可以使用動態代理,它相當于定義了一個通用化的代理模板,我們不需要預先定義代理類,它會根據你在客戶端使用的“抽象主題型別”動態創建代理物件,只要你使用的目標物件使用了代理模式,這個通用的代理模板都會為目標物件動態的生成代理類,并且我們不需要在代理類中去實作代理行為,它會有一種通用的呼叫方式,將代理擴展的行為作用于每個方法,
5.2.DispatchProxy
下面我們將使用System.Reflection命名空間下的DispatchProxy型別來實作動態代理,該型別只適用于.NET框架4.6以上版本和.NET Core,對于較低版本的.NET框架不支持,
我們將延用靜態代理中的“抽象主題”和“真實主題”,在此基礎之上撰寫動態代理類,該代理類主要代理系統中服務類的“增刪改查”行為,并在各個服務類的“增刪改查”方法之前和之后加上身份驗證和日志記錄,具體代碼如下:
1.創建動態代理型別
1 /// <summary> 2 /// 動態代理類 3 /// </summary> 4 /// <typeparam name="T">抽象主題型別</typeparam> 5 public class ProxyCRUD<T> : DispatchProxy 6 { 7 //目標物件,被代理物件 8 public T Target { get; private set; } 9 10 /// <summary> 11 /// 創建“動態代理類”物件,并指定一個“被代理物件” 12 /// </summary> 13 /// <param name="target">被代理物件</param> 14 /// <returns>抽象主題型別(代理介面),但型別的參考指向的是“動態代理物件”</returns> 15 public static T Decorate(T target) 16 { 17 //創建一個實作“抽象主題介面”的“動態代理物件” 18 dynamic proxy = Create<T, ProxyCRUD<T>>(); 19 20 //指定“動態代理物件”代理的目標物件,即被代理的物件 21 proxy.Target = target; 22 23 return proxy; 24 } 25 // END Decorate() 26 27 /// <summary> 28 /// 動態代理物件執行代理行為 29 /// “被代理物件”的方法被代理物件執行時,會通過該方法間接呼叫 30 /// </summary> 31 /// <param name="targetMethod">“被代理物件”的方法資訊</param> 32 /// <param name="args">方法的引數</param> 33 /// <returns>方法執行的回傳值</returns> 34 protected override object? Invoke(MethodInfo? targetMethod, object?[]? args) 35 { 36 if (Validate()) //擴展通用處理:身份驗證 37 { 38 //通過反射的方式呼叫“被代理物件”的原始方法 39 var result = targetMethod.Invoke(Target,args); 40 41 Log(targetMethod.Name);//擴展通用處理:日志記錄 42 43 return result; 44 } 45 else 46 { 47 return null; 48 } 49 50 }// END Invoke () 51 52 /// <summary> 53 /// 身份驗證(偽代碼) 54 /// </summary> 55 public bool Validate() 56 { 57 //偽代碼,模擬獲取用戶資訊 58 string currentUserId = "張三"; 59 60 if (currentUserId == "張三") 61 { 62 Console.WriteLine($"“{currentUserId}”用戶的權限認證成功!"); 63 return true; 64 } 65 else 66 { 67 Console.WriteLine($"“{currentUserId}”用戶的權限認證失敗!"); 68 return false; 69 } 70 71 } // END Validate() 72 73 /// <summary> 74 /// 日志記錄(偽代碼) 75 /// </summary> 76 public void Log(string action) 77 { 78 //偽代碼,模擬獲取用戶資訊 79 string currentUserId = "張三"; 80 81 Console.WriteLine($"用戶:{currentUserId}在{DateTime.Now}執行了{action}操作,"); 82 83 }// END Log() 84 85 }
以上代碼中的“動態代理類”是一個泛型類,其中泛型的型別引數,需要指定代理模式中的“抽象主題型別”,也就是被代理類和代理類都需要實作的介面型別,在靜態模式中,“抽象主題型別”是指定的一個具體型別,而這里使用了泛型的型別引數,這就意味該類可以適用于所有型別的代理,就像List<T>一樣,不光可以用于List<int>集合、還可以用于List<string>、List<object>等,
其中派生自“DispatchProxy”類,實作的Invoke方法是代理行為的核心,在呼叫層通過代理物件呼叫任何方法時,都會將方法的執行帶入到Invoke方法中,換句話說,我們使用動態代理物件去執行方法時,就像通過“傳送門”就方法的執行轉發到Invoke方法中,然后在該方法中可以在原始方法的基礎上額外擴展其他功能,
2.客戶端呼叫
1 //創建真實主題物件,即被代理物件 2 UserService userService = new UserService(); 3 4 /*【創建代理物件】 5 * 根據“抽象主題介面”動態創建代理物件,并實作“抽象主題介面” 6 * “被代理物件”作為引數指定給了“代理物件” 7 */ 8 var proxyUserService = ProxyCRUD<IUserService>.Decorate(userService); 9 10 /* 11 * 方法源于“抽象主題”,實作源于“被代理物件”, 12 * “代理物件”代理了方法的呼叫, 13 */ 14 var userList = proxyUserService.GetUserList(); 15 16 Console.WriteLine("\r\n輸出用戶資訊:"); 17 foreach (var user in userList) 18 { 19 Console.WriteLine(user); 20 }
6.代理和裝飾
代理模式和裝飾模式在實作時有些類似,但是代理模式主要是給“真實主題類”增加一些全新的職責,例如在業務方法執行之前進行權限驗證、例如在業務方法執行之后附加日志記錄等,這些職責往往是非業務的,與業務職責不屬于同一個問題域,
對于裝飾模式而言,它是通過裝飾類為具體構建類增加一些與業務職責相關的職責,是對原有業務職責的擴展,擴展的職責和原有業務都屬于同一個問題域,代理模式和裝飾模式的目的也不相同,代理模式達到控制物件的訪問,而裝飾模式是為物件動態地增加功能,可以看作是填補繼承不靈活性的另一種功能復用方案,
7.總結
代理模式的結構是比較簡單的,實際上就是將某個型別的“代理需求”(類的行為/方法/業務)建立一個“抽象主題”(介面)并提供方法的實作,然后我們面向這個“抽象主題”創建一個代理類,并在代理類中參考“被代理物件”,然后在“被代理物件”的“行為/方法/業務”執行的基礎上進行額外的加工、管控,
代理模式的應用場景非常廣泛,難點就在如何應用到不同場景,并且不同場景還涉及到其他領域的特有技術,其中常用的應用場景包括:遠程代理、虛擬代理、保護代理、智能參考代理,以及AOP的實作,本文中的示例是針對“智能參考代理”場景的應用,也就是在目標物件原有的業務方法之上,為物件提供一些額外的通用處理,
本文屬于代理模式的基礎教程,所以在此不能詳細闡述所有的應用場景,下面根據較常用的場景進行簡單概要:
- 遠程代理:當你的主機想要訪問遠程主機中的物件時,可以使用遠程代理幫你建立一個網路橋梁,它會幫你訪問網路轉發請求來完成遠程物件的呼叫,
- 虛擬代理:當加載的物件資源大、耗時長,可以使用虛擬代理為這種物件建立一個輕量級的替身物件先預載,從而降低系統開銷、縮短運行時間時,
- 保護代理:當需要控制對一個物件的訪問,為不同用戶提供不同級別的訪問權限時,可以使用保護代理,
- 智能參考代理:當訪問某個物件的行為需要做一些額外的擴展操作時,可以使用智能參考代理,
知識改變命運
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/545368.html
標籤:設計模式
下一篇:“無所不能的中介”——代理模式
