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

.NET 委派深度解析(上):從強耦合到鬆耦合的演進之路

     編輯:關於C語言

前言

委派是 C# 型別安全的函數指標,更是事件機制基石。本文透過「工人 Peter、壞老闆 Boss、宇宙 Universe」故事,展示從強型別耦合 → 介面 → 委派 → 事件 → 靜態監聽者 → 非同步的架構演進歷程。

階段 1:強型別耦合 (Hard-coded Dependency)

class Worker
{
    private Boss _boss;
    public void Advise(Boss boss) => _boss = boss;

    public void DoWork()
    {
        Console.WriteLine("Worker: work started");
        _boss?.WorkStarted();
        Console.WriteLine("Worker: work progressing");
        _boss?.WorkProgressing();
        Console.WriteLine("Worker: work completed");
        if (_boss != null)
        {
            int grade = _boss.WorkCompleted();
            Console.WriteLine($"Worker grade = {grade}");
        }
    }
}

class Boss
{
    public void WorkStarted() { }
    public void WorkProgressing() { }
    public int WorkCompleted() => 2; // 打分 2/10
}

缺點:Worker 綁死 Boss 類別,無法擴充 Universe、無法單獨測試、違反開放封閉原則。

階段 2:介面抽離 (Interface Abstraction)

interface IWorkerEvents
{
    void WorkStarted();
    void WorkProgressing();
    int WorkCompleted();
}

class Worker
{
    private IWorkerEvents _events;
    public void Advise(IWorkerEvents events) => _events = events;
    // DoWork 呼叫 _events.XXX()
}

class Boss : IWorkerEvents { ... } // 實作介面

進步:依賴抽象而非具體類別。缺點:介面粒度過粗,Boss 被迫實作不關心的方法 (介面隔離原則違反)。

階段 3:委派細粒度解耦 (Delegate Granularity)

將介面拆解為單一方法委派:

delegate void WorkStarted();
delegate void WorkProgressing();
delegate int WorkCompleted();

class Worker
{
    public WorkStarted Started;
    public WorkProgressing Progressing;
    public WorkCompleted Completed;

    public void DoWork()
    {
        Console.WriteLine("Worker: work started");
        Started?.Invoke();
        Console.WriteLine("Worker: work progressing");
        Progressing?.Invoke();
        Console.WriteLine("Worker: work completed");
        if (Completed != null)
        {
            int grade = Completed(); // 僅取最後結果
            Console.WriteLine($"Worker grade = {grade}");
        }
    }
}

class Boss
{
    public int WorkCompleted() => 4; // 只關心完成評分
}

優點:Boss 只需提供關心的方法,委派即「單一方法介面」。

階段 4:靜態監聽者 (Static Listeners)

Universe 為單例/靜態類別,無實例:


class Universe
{
    static void WorkerStartedWork() => Console.WriteLine("Universe notices...");
    static int WorkerCompletedWork() => 7;
}

static void Main()
{
    var peter = new Worker();
    var boss = new Boss();
    peter.Completed = boss.WorkCompleted;           // 覆寫!
    peter.Started = Universe.WorkerStartedWork;     // 掛鉤靜態方法
    peter.Completed = Universe.WorkerCompletedWork; // 再次覆寫,Boss 遺失!
}

問題:委派欄位為單一指派 (=),無法多播,後設定者覆蓋前者。

階段 5:多播委派與事件封裝 (Multicast + Event)

多播委派語法


peter.Completed += boss.WorkCompleted;           // += 累加
peter.Started += Universe.WorkerStartedWork;
peter.Completed += Universe.WorkerCompletedWork; // 現在兩者都能收到

事件關鍵字:封裝 +=/-=,防止外部誤觸


class Worker
{
    public event WorkStarted Started;     // 編譯器產生 add/remove 存取子
    public event WorkProgressing Progressing;
    public event WorkCompleted Completed;

    // 內部仍可直接 Invoke
    protected virtual void OnCompleted()
    {
        Completed?.Invoke();
    }
}

事件限制:外部只能 +=/-=,不能 = 覆寫、不能直接 Invoke()、不能清空清單。

完整事件版輸出


Worker: work started
Universe notices worker starting work
Worker: work progressing
Worker: work completed
Better...
Universe pleased with worker's work
Worker grade = 7  // 僅顯示最後一個回傳值 (Universe 的 7)
Main: worker completed work

注意:多播委派有回傳值時,同步呼叫僅保留最後一個結果,這正是下集將解決的問題。

委派 vs 介面:何時用哪個?

維度介面委派
方法數量多個相關方法單一方法
實作方類別實作介面任何符合簽名的方法 (實例/靜態/Lambda)
多播不支援原生支援 (+=)
狀態實例擁有狀態無狀態 (閉包除外)
適用場景契約、外掛架構、DI回調、事件、策略、非同步

總結:演進路徑心智圖


強耦合 (Boss 類別)
    ↓ 抽離介面
介面 (IWorkerEvents) - 粒度太粗
    ↓ 拆解單一方法
委派欄位 (WorkStarted 等) - 無法多播
    ↓ += 多播
多播委派 - 外部可誤觸 (=, Invoke)
    ↓ event 封裝
事件 - 安全、慣用、支援標準模式
    ↓ 非同步模式 (下集)
APM / TAP

下集預告

  • 多播委派回傳值問題 → GetInvocationList()
  • 非同步委派四種模式:火後不理、輪詢、回調、Task/await
  • 執行緒池、同步上下文、例外處理最佳實務
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved