你想買日本限定商品,但人在台灣。你拜託在日本的朋友幫忙下單、收貨、國際轉運。朋友就是代理——他隱藏了「跨國購物」的複雜細節(語言、付款、物流、關稅),你只需說「幫我買這個」。
| 類型 | 目的 | 典型案例 |
|---|---|---|
| 遠程代理 | 跨進程/跨機器呼叫 | Web Service、WCF、gRPC Client Proxy |
| 虛擬代理 | 延遲建立昂貴物件 | 大圖片載入、資料庫連線池、延遲載入屬性 |
| 保護代理 | 存取控制、權限檢查 | API Gateway、RBAC 攔截器 |
| 智慧引用 | 引用時附加行為 | 呼叫計數、記憶體快照、鎖定管理 |

public interface IImage
{
void Display(); // 顯示圖片
}
public class HighResolutionImage : IImage
{
private readonly string _filePath;
public HighResolutionImage(string filePath)
{
_filePath = filePath;
LoadFromDisk(); // 模擬耗時載入
}
private void LoadFromDisk()
{
Console.WriteLine($"[Real] 正在從磁碟載入大圖:{_filePath} ...");
Thread.Sleep(1000); // 模擬 I/O
Console.WriteLine("[Real] 載入完成");
}
public void Display() => Console.WriteLine($"[Real] 顯示圖片:{_filePath}");
}
public class ImageProxy : IImage
{
private readonly string _filePath;
private HighResolutionImage _realImage; // 延遲建立
private int _displayCount = 0;
public ImageProxy(string filePath) => _filePath = filePath;
public void Display()
{
// 1. 智慧引用:計數
_displayCount++;
Console.WriteLine($"[Proxy] Display 被呼叫第 {_displayCount} 次");
// 2. 虛擬代理:首次使用時才建立真實物件
if (_realImage == null)
{
Console.WriteLine("[Proxy] 首次顯示,建立真實圖片物件...");
_realImage = new HighResolutionImage(_filePath);
}
// 3. 轉發給真實物件
_realImage.Display();
// 4. 可在這裡加入快取、記憶體釋放等邏輯
}
// 額外功能:取得呼叫統計
public int GetDisplayCount() => _displayCount;
}
class Program
{
static void Main()
{
// 建立代理(此時尚未載入圖片)
IImage image = new ImageProxy("nature_4k.jpg");
Console.WriteLine("=== 程式啟動,準備顯示 ===");
image.Display(); // 第一次:觸發載入
image.Display(); // 第二次:直接用快取物件
image.Display(); // 第三次
Console.WriteLine($"\n總共顯示了 {((ImageProxy)image).GetDisplayCount()} 次");
Console.ReadKey();
}
}
=== 程式啟動,準備顯示 ===
[Proxy] Display 被呼叫第 1 次
[Proxy] 首次顯示,建立真實圖片物件...
[Real] 正在從磁碟載入大圖:nature_4k.jpg ...
[Real] 載入完成
[Real] 顯示圖片:nature_4k.jpg
[Proxy] Display 被呼叫第 2 次
[Real] 顯示圖片:nature_4k.jpg
[Proxy] Display 被呼叫第 3 次
[Real] 顯示圖片:nature_4k.jpg
總共顯示了 3 次
手寫 Proxy 類別對每個介面都要寫一遍。.NET 生態提供動態代理庫,執行期自動產生 Proxy:
public class LoggingProxy : DispatchProxy
{
private object _target;
public static T Create(T target)
{
var proxy = Create();
((LoggingProxy)proxy)._target = target;
return proxy;
}
protected override object Invoke(MethodInfo targetMethod, object[] args)
{
Console.WriteLine($"[Log] 呼叫 {targetMethod.Name} 參數: {string.Join(", ", args)}");
var result = targetMethod.Invoke(_target, args);
Console.WriteLine($"[Log] 回傳: {result}");
return result;
}
}
// 使用
var real = new HighResolutionImage("test.jpg");
var proxy = LoggingProxy.Create(real);
proxy.Display(); // 自動記錄
| 優點 | 缺點 |
|---|---|
| ✅ 控制存取(權限、延遲、遠程) ✅ 隱藏複雜度(單一職責) ✅ 開放封閉原則(不修改 RealSubject 新增行為) ✅ 可組合多重代理(Logging → Caching → Auth) |
⚠️ 增加間接層,微增延遲 ⚠️ 程式碼較多(靜態代理) ⚠️ 若代理邏輯過重,違反單一職責 |
代理模式以「間接層」解決「直接存取」的諸多問題。現代開發中,靜態代理適合特定場景(如虛擬代理);動態代理則是 AOP、攔截器、Mock 框架的基石。掌握兩者,讓你在架構層面游刃有餘。