概念界定
在講解代理模式之前,我們需要區分一下委託、代理、中介三者的概念,因為很多人可能並不清楚他們之間的區別,甚至認為沒有區別。但是,如果對這三個概念沒有清晰的界定,很可能會在學習的過程中一頭霧水,可能會覺得代理模式跟誰都很像,跟誰都容易混淆。
委託(Delegate)
委託跟代理是相對的,通常我們說"A委託B辦某事",也相當於在說"B代理A辦某事",因此,委託和代理通常可以認為是等價的,這也是為什麼很多時候代理模式也可以叫委託模式了,但是,在C#中,委託被賦予了新的含義,它是一種引用方法的型別,相當於C++
裡的函式指標。下面是委託的一個簡單實現,但這只是為了說明委託在C#中新的含義,不是本文的重點,看看就好。
public delegate int Calc(int x, int y);
class Program
{
static void Main(string[] args)
{
Calc calc = new Calc(Add);
int result = calc(1, 2);
Console.WriteLine(result);
calc = Subtract;
result = calc(1, 2);
Console.WriteLine(result);
}
public static int Add(int x, int y)
{
return x + y;
}
public static int Subtract(int x, int y)
{
return x - y;
}
}
代理(Proxy)
如下圖所示,代理的作用就是代替D(被代理者)完成某一件事,A、B、C訪問了代理就等同訪問了D。看似說了句廢話,其實不然,這裡最關鍵的一點就是A、B、C本來是可以訪問D的,但是由於D的懶惰,或者訪問過程比較困難甚至受阻(比如線路被切斷),因此才有了代理,有了代理後,A、B、C就不用再訪問D了,代理會全權處理來自A、B、C一切請求。見他如見我就是代理,如欽差大臣,產品代理商,代購,租房代理等。
中介(Mediator)
如下圖所示,中介是在一組複雜的關係中牽線搭橋,使得A、B、C、D相互之間的交流變得簡單,中介不能完全替代A、B、C、D中的任何一方,牽線搭橋之後,被牽線的雙方還是要見面的,如租房中介,婚姻中介等,婚姻中介介紹相親的雙方認識,但你不能要求婚姻中介替你談戀愛甚至結婚生子,但是代理可以。
我們之所以區分不清楚,是因為生活中很多時候就沒區分清楚,例如,我們很多時候認為租房中介就是租房代理,而事實並非如此,僅僅是因為很多時候一個機構同時具備了兩種角色而已,簡單總結一下:
- 委託和中介是等價的,只是主謂方向剛好發生了對調;
- 代理可以全權代理被代理者,被代理者自始至終都可以不用跟訪問者互動,而中介只是牽線,最終被牽線的雙方還是要互動的;
- 代理通常解決的是多對一的關係,而中介解決的是多對多的關係,見他如見我是代理,牽線搭橋是中介,好好體會一下其中的區別。
定義
書歸正傳,代理模式就是為其他物件提供一種代理以控制對這個物件的訪問。
使用場景
Windows快捷方式,VPN,防火牆,RPC呼叫,翻牆等。
目的
- 在不改變原有程式碼的基礎上,對原有類加以控制,如下加日誌和異常捕獲體現的就是一種訪問控制:
public interface IUserRepository { User Get(int id); } public class UserRepository : IUserRepository { private static readonly List<User> _users = new List<User> { new User{Id=1,Name="zs"}, new User{Id=2,Name="ls" }, new User{Id=3,Name="ww" } }; public User Get(int id) { return _users .FirstOrDefault(u => u.Id == id); } } public class UserRepositoryProxy : IUserRepository { private readonly IUserRepository _userRepository = new UserRepository(); private readonly ILogger<UserRepositoryProxy> _logger; public UserRepositoryProxy(ILogger<UserRepositoryProxy> logger) { this._logger = logger; } public User Get(int id) { try { _logger.LogDebug("UserRepositoryProxy-Get In:id={0}", id); return _userRepository.Get(id); } catch (Exception ex) { _logger.LogError(ex, "UserRepositoryProxy-Get Error."); throw; } } }
- 訪問由於某種原因不能直接訪問或者直接訪問困難的第三方元件或中介軟體,如下面的HTTP請求:
public interface IUserProxy { string GetUserById(int id); } public class UserProxy : IUserProxy { public string GetUserById(int id) { using (var client = new HttpClient()) { var result = client .GetAsync($"https://localhost:5001/user?id={id}") .GetAwaiter() .GetResult(); return result.Content .ReadAsStringAsync() .GetAwaiter() .GetResult(); } } }
UML類圖
代理模式通常採用組合的方式實現,因為被代理者往往不希望被客戶端直接訪問,當然,也並不是任何時候都有明確的被代理物件,例如上面的HTTP請求就不知道具體代理的是誰。但是,可以很明顯的看出,這裡是代理模式,代理的是對網路API介面的請求。因此,代理模式重在思想,而並非程式碼結構,如果某種場景程式碼結構和其他模式類似,或者和上面的UML類圖完全不同也不用覺得奇怪。
和介面卡比較
代理模式和介面卡模式看似都是在兩個物件之間建立橋樑,使二者可以相互通訊,因此他們的程式碼結構有時候是一樣的,但是他們之間有明顯的區別:
- 介面卡模式的目的是介面轉換,使原本不相容而不能一起工作的類可以一起工作;
- 代理模式的目的是間接訪問和訪問控制;
- 介面卡模式面向的是不能一起工作的兩個類,而代理模式是面向原本可以一起工作的兩個類。
總結
隨著系統複雜度的發展,代理模式更偏向於是一種架構模式,在各種框架中以及與各種中介軟體互動是是非常常見的,而在我們自己的程式碼中反而很少見了,它更多的體現的是一種思想,而非程式碼實現。相對於如何實現代理模式,更重要的應該是什麼時候什麼場景應該使用代理模式,知道了什麼時候什麼場景使用,就不會糾結實現出來的像介面卡模式還是像裝飾模式了,即使像也僅僅是長得像而已,本質是完全不同的。