跳至頁尾內容
Iron Academy Logo
學習C#
學習C#

其他類別

雙重分派C#:何時注入依賴是有意義的 — 通過Derek Comartin的領域驅動設計範例解析

Derek Comartin
8m 44s

在C#程式語言領域中,雙重調度經常被誤解或未充分利用。 這是一種強大的技術,能夠在涉及的兩個物件之間實現多態行為,特別是在處理派生類的行為時。

在他的视频"Double Dispatch in DDD: When Injecting Dependencies Makes Sense"中,Derek Comartin 解釋了雙重調度如何完美地融入領域驅動設計(DDD)中。 我們將深入探索他的例子,展示如何減少維護負擔、簡化現有程式碼,甚至模仿其他語言中常見的訪客模式。

如果您曾經面對C#中單一調度的限制,或試圖根據物件實例和引入的策略或規則來決定行為,這篇文章將幫助您釐清如何有效地使用C#的雙重調度。

為什麼在領域模型中引入行為是合理的

Derek首先指出在DDD中一個常見的教條——您的領域模型應該完全沒有依賴。 但這並不總是實用或有用的。 在建模行為時,您可能需要引入邏輯,如規則、政策或策略。 這就是雙重調度的用武之地:您可以將領域概念(如政策)傳遞到領域物件中,並讓基於方法的評估在外部處理——但領域物件仍然具有最終控制權。

當您考慮您所攜帶的東西時,此模式是合理的:領域行為,而不是基礎設施。

糟糕的例子:領域中的硬編程邏輯

為了展示問題,Derek以一個包含硬編程邏輯以確定晚到的運輸類開始:

public bool IsLate(DateTime expectedDelivery)
{
    return _systemClock.Now() > expectedDelivery;
}
public bool IsLate(DateTime expectedDelivery)
{
    return _systemClock.Now() > expectedDelivery;
}

這現有的程式碼簡單但不靈活。 它依賴於編譯時的決定,並且將行為緊密結合到類中。 更改規則需要更改領域模型,並且測試更加困難,因為它取決於時間。

重構:使用政策和雙重調度

Derek引入介面IDeliveryTimingPolicy:

public interface IDeliveryTimingPolicy
{
    bool IsLate(Shipment shipment);
}
public interface IDeliveryTimingPolicy
{
    bool IsLate(Shipment shipment);
}

然後他建立了兩個實現:

  1. StandardDeliveryTimingPolicy

  2. BufferedDeliveryTimingPolicy

這些類接受運輸並根據其規則返回布林值。 以下是來自BufferedDeliveryTimingPolicy的程式碼片段:

public bool IsLate(Shipment shipment)
{
    return _clock.Now() > shipment.DeliveryDate.AddMinutes(30);
}
public bool IsLate(Shipment shipment)
{
    return _clock.Now() > shipment.DeliveryDate.AddMinutes(30);
}

現在在運輸類中,我們使用雙重調度

public bool IsLate(IDeliveryTimingPolicy policy)
{
    return policy.IsLate(this);
}
public bool IsLate(IDeliveryTimingPolicy policy)
{
    return policy.IsLate(this);
}

這是經典的雙重調度:第一次調度是對運輸調用IsLate()並傳遞政策; 第二次調度是對政策調用IsLate(),並將運輸作為參數。 涉及兩個物件,每個物件都參與確定行為——這是單一調度無法做到的。

測試和靈活性的好處

這種方法導致高度可測試和確定性的程式碼。 Derek展示了使用標準和緩衝政策的例子,其中測試資料是可控的,每個物件的運行時型別決定行為。

var policy = new BufferedDeliveryTimingPolicy(TimeSpan.FromMinutes(30));
var shipment = new Shipment { DeliveryDate = DateTime.UtcNow.AddMinutes(-15) };
Assert.False(shipment.IsLate(policy)); // Because of 30-minute buffer
var policy = new BufferedDeliveryTimingPolicy(TimeSpan.FromMinutes(30));
var shipment = new Shipment { DeliveryDate = DateTime.UtcNow.AddMinutes(-15) };
Assert.False(shipment.IsLate(policy)); // Because of 30-minute buffer

這展示了如何在不更改領域模型的情況下改變運行時行為。 您可以根據政策的動態性質獲得多態行為,同時保持領域完整性。

C# 中的雙重調度與訪客模式

Derek的例子類似於訪客模式,其中您定義了一組操作以便在不改變結構的情況下對物件進行操作。 通常,在訪客模式中,您會看到類似的東西:

public abstract class Shape
{
    public abstract void Accept(IVisitor visitor);
}

public class Circle : Shape
{
    public override void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}
public abstract class Shape
{
    public abstract void Accept(IVisitor visitor);
}

public class Circle : Shape
{
    public override void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

在這裡,Accept()和Visit()方法在兩種型別之間協調——物件和訪客——就像Derek使用的運輸和IDeliveryTimingPolicy一樣。 這種模式甚至在像C#這樣的單一調度語言中,也有助於實現多重調度行為。

通過政策集合組成規則

在接下來的另一個例子中,Derek展示了如何通過集合評估多個規則。 他引入了如下規則:

  • HasValidDestinationRule

  • AllPackagesPackedRule

  • NotAlreadyShippedRule

每個都實現了IShipmentReadinessRule:

public bool IsSatisfiedBy(Shipment shipment)
public bool IsSatisfiedBy(Shipment shipment)

然後,運輸類這樣評估它們:

public bool CanShip(IEnumerable<IShipmentReadinessRule> rules)
{
    return rules.All(rule => rule.IsSatisfiedBy(this));
}
public bool CanShip(IEnumerable<IShipmentReadinessRule> rules)
{
    return rules.All(rule => rule.IsSatisfiedBy(this));
}

這種可接受的空模式允許動態評估多個領域規則。 如果您將規則儲存在配置中(尤其是在多租戶應用中),可以在運行時建立一個新列表並將其傳遞給運輸。

多租戶應用中的動態規則

Derek指出,在多租戶應用中,規則集可能會因為客戶而改變。 您可以從儲存中獲取政策列表並動態注入它們:

var rules = LoadRulesForCustomer(customerId);
var canShip = shipment.CanShip(rules);
var rules = LoadRulesForCustomer(customerId);
var canShip = shipment.CanShip(rules);

這展示了如何將動態調度和運行時決策層次化到應用中。 所需行為是通過改變配置,而不是模型來實現的。

澄清誤解:並非所有依賴都是壞的

最後,Derek闡述了一個誤解,即將任何事物注入領域都是壞的。 他強調的是注入了什麼才是重要的。 注入領域行為,如規範或政策,與注入基礎設施不同。

領域模型仍然是入口點。 它擁有決策權,但可以將其委派給其他物件,只要它們在同一個領域上下文中。

總結:為什麼在DDD中雙重調度C#非常強大

Derek最後敦促我們要批判性地思考:當它們帶來清晰度和可維護性時,不要害怕使用空訪客、公用虛擬空接收或類似模式。 當您以一種受控的方式注入業務邏輯時,您可以獲得靈活性和精確性。

因此,無論您是在製作一個新的類還是重構現有的程式碼庫,雙重調度C#為您提供了一種清晰的方法來分離關注點,同時保持對領域的專注。

如果您正在使用政策、規範,甚至毫無察覺地構建訪客模式,那麼您已經接近於實現雙重調度了。 理解它讓您更好地掌控程式碼在運行時的行為,提高可測試性和適應性。

結論

總之:C#中的雙重調度可以是一個優雅且實用的解決方案,用於注入領域邏輯、保持封裝性和支持靈活的行為。 當與訪客、多重調度、動態關鍵字使用(謹慎)等模式結合使用時,能夠撰寫表達力強大、健全的領域模型。

所以,下次您調用shipment.IsLate(policy)時,請意識到您正在利用一種基本的模式,將C#推向真正的多態設計。

範例提示:如果您正在建立一個PurchaseOrder類,並希望根據政策確定能否新增一個項目,嘗試將政策傳遞給PurchaseOrder,讓政策存取訂單。 這就是雙重調度正在發揮作用。

在他的YouTube頻道上觀看完整影片,以獲得更多關於這個主題的見解。

Hero Worlddot related to 雙重分派C#:何時注入依賴是有意義的 — 通過Derek Comartin的領域驅動設�...
Hero Affiliate related to 雙重分派C#:何時注入依賴是有意義的 — 通過Derek Comartin的領域驅動設�...

分享您所愛以賺取更多報酬

您是否為使用 .NET、C#、Java、Python 或 Node.js 的開發者建立內容?將您的專業知識轉化為額外收入!

Iron 支援團隊

我們線上24小時,每週5天。
聊天
電子郵件
給我打電話