首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 設計模式:代理模式 為物件加上保護層與控制層

     編輯:關於C語言

生活場景:請朋友代購

你想買日本限定商品,但人在台灣。你拜託在日本的朋友幫忙下單、收貨、國際轉運。朋友就是代理——他隱藏了「跨國購物」的複雜細節(語言、付款、物流、關稅),你只需說「幫我買這個」。

代理模式四大核心變體

類型目的典型案例
遠程代理跨進程/跨機器呼叫Web Service、WCF、gRPC Client Proxy
虛擬代理延遲建立昂貴物件大圖片載入、資料庫連線池、延遲載入屬性
保護代理存取控制、權限檢查API Gateway、RBAC 攔截器
智慧引用引用時附加行為呼叫計數、記憶體快照、鎖定管理

類別結構

代理模式結構圖

  • Subject:共同介面
  • RealSubject:真正執行工作的物件
  • Proxy:持有 RealSubject 參考,控制存取並可附加行為

完整實作:虛擬代理 + 智慧引用(延遲載入大圖片 + 計數)

1. 共同介面

public interface IImage
{
    void Display(); // 顯示圖片
}

2. 真實主題(昂貴物件)

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}");
}

3. 代理(延遲載入 + 計數 + 快取)

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;
}

4. 客戶端使用

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:

  • Castle DynamicProxy:AOP 框架基石(如 ABP、Autofac 攔截器)
  • DispatchProxy:.NET Core 內建,無第三方依賴
  • Moq / NSubstitute:測試專用 Mock 物件

DispatchProxy 範例(記錄所有方法呼叫)


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 框架的基石。掌握兩者,讓你在架構層面游刃有餘。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved