共用方式為


教學課程:使用預設介面方法更新介面

您可以在宣告介面成員時定義實作。 最常見的案例是安全地將成員新增至已發行並供無數用戶端使用的介面。

在本教學課程中,您將了解如何:

  • 藉由添加具備實作的方法來安全地擴展介面。
  • 建立參數化實作,以提供更大的彈性。
  • 使執行者能以覆寫的方式提供更具體的實現。

先決條件

您需要設定電腦,以執行 .NET (包括 C# 編譯器)。 C# 編譯程式適用於 Visual Studio 2022.NET SDK

案例概觀

本教學課程從客戶關係連結庫的第1版開始。 您可以在 GitHub 上的範例存放庫上取得入門應用程式。 建置此連結庫的公司預期有現有應用程式的客戶採用其連結庫。 他們為程式庫的使用者提供最基本的介面定義以進行實作。 以下是客戶的介面定義:

public interface ICustomer
{
    IEnumerable<IOrder> PreviousOrders { get; }

    DateTime DateJoined { get; }
    DateTime? LastOrder { get; }
    string Name { get; }
    IDictionary<DateTime, string> Reminders { get; }
}

他們定義了代表訂單的第二個介面:

public interface IOrder
{
    DateTime Purchased { get; }
    decimal Cost { get; }
}

從這些介面,小組可以為其使用者建置連結庫,為客戶建立更好的體驗。 其目標是建立與現有客戶更深入的關係,並改善與新客戶的關係。

現在,是時候為下一個版本升級函式庫了。 其中一項要求的功能可為擁有大量訂單的客戶提供忠誠度折扣。 每當客戶下訂單時,就會套用這個新的忠誠度折扣。 特定折扣是每個個別客戶的屬性。 的每個實作 ICustomer 都可以為忠誠度折扣設定不同的規則。

最自然的方式來新增這項功能是透過一個方法來提升 ICustomer 介面,以便套用任何忠誠度折扣。 此設計建議在經驗豐富的開發人員中引起了關注:「一旦發行介面,介面就不可變了! 不要進行重大變更!您應該使用預設介面實作來升級介面。 連結庫作者可以將新成員新增至 介面,併為這些成員提供預設實作。

默認介面實作可讓開發人員升級介面,同時仍可讓任何實作者覆寫該實作。 程式庫的使用者可以使用預設實作作為不影響的變更。 如果商務規則不同,則可以覆寫。

使用預設介面方法升級

小組就最有可能的預設實作達成一致:客戶的忠誠度折扣。

升級應該提供設定兩個屬性的功能:符合折扣資格的訂單數目,以及折扣的百分比。 這些功能使其成為預設介面方法的完美案例。 您可以將 方法新增至 ICustomer 介面,並提供最有可能的實作。 所有現有實作和任何新實作都可以使用默認實作,或自行提供。

首先,將新方法新增至 介面,包括 方法的主體:

// This method belongs in the ICustomer interface (ICustomer.cs).
// Version 1:
public decimal ComputeLoyaltyDiscount()
{
    DateTime TwoYearsAgo = DateTime.Now.AddYears(-2);
    if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > 10))
    {
        return 0.10m;
    }
    return 0;
}

備註

上述範例會在本教學課程中為簡單起見使用 DateTime.Now.AddYears(-2) 。 請注意,DateTime 計算可能在夏令時間轉換以及閏年時出現邊界情況。 針對生產程序代碼,請考慮在精確度很重要時使用 UTC 時間或更健全的日期計算方法。

連結庫作者撰寫了第一個測試來檢查實作:

SampleCustomer c = new SampleCustomer("customer one", new DateTime(2010, 5, 31))
{
    Reminders =
    {
        { new DateTime(2010, 08, 12), "childs's birthday" },
        { new DateTime(2012, 11, 15), "anniversary" }
    }
};

SampleOrder o = new SampleOrder(new DateTime(2012, 6, 1), 5m);
c.AddOrder(o);

o = new SampleOrder(new DateTime(2013, 7, 4), 25m);
c.AddOrder(o);

// Check the discount:
ICustomer theCustomer = c;
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");

請注意測試的下列部分:

// Check the discount:
ICustomer theCustomer = c;
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");

SampleCustomerICustomer 的隱含轉換是必要的。 類別SampleCustomer不需要提供ComputeLoyaltyDiscount的實作;這是由ICustomer介面提供的。 不過,類別 SampleCustomer 不會從其介面繼承成員。 該規則尚未變更。 為了呼叫介面中宣告和實作的任何方法,變數必須是 介面的類型, ICustomer 在此範例中。

提供參數設定功能

默認實作太嚴格。 此系統的許多取用者可能會為購買次數、不同成員資格長度或不同的百分比折扣選擇不同的閾值。 您可以藉由提供設定這些參數的方式,為更多客戶提供更好的升級體驗。 讓我們新增靜態方法,以設定控制默認實作的這三個參數:

// These methods belong in the ICustomer interface (ICustomer.cs).
// Version 2:
public static void SetLoyaltyThresholds(
    TimeSpan ago,
    int minimumOrders = 10,
    decimal percentageDiscount = 0.10m)
{
    length = ago;
    orderCount = minimumOrders;
    discountPercent = percentageDiscount;
}
private static TimeSpan length = new TimeSpan(365 * 2, 0,0,0); // two years
private static int orderCount = 10;
private static decimal discountPercent = 0.10m;

public decimal ComputeLoyaltyDiscount()
{
    DateTime start = DateTime.Now - length;

    if ((DateJoined < start) && (PreviousOrders.Count() > orderCount))
    {
        return discountPercent;
    }
    return 0;
}

該小型代碼段顯示許多新的語言功能。 介面現在可以包含靜態成員,包括字段和方法。 也會啟用不同的存取修飾詞。 其他欄位是私用的,新的方法是公用的。 在介面成員上可以使用任何修飾詞。

使用一般公式來計算忠誠度折扣但不同參數的應用程式不需要提供自定義實作;他們可以透過靜態方法設定自變數。 例如,下列程式代碼會設定「客戶感謝」,以獎勵擁有超過一個月會員資格的客戶:

ICustomer.SetLoyaltyThresholds(new TimeSpan(30, 0, 0, 0), 1, 0.25m);
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");

擴充預設實作

您到目前為止新增的程式代碼為那些使用者想要類似默認實作的情境或提供一組不相關規則的情況,提供了便利的實作。 針對最後的功能調整,我們稍微重構程式碼,使得使用者能夠在預設實作的基礎上擴展或修改。

請考慮想要吸引新客戶的初創公司。 他們提供 50% 折扣,以折扣新客戶的第一筆訂單。 否則,現有的客戶會取得標準折扣。 程式庫作者需要將預設實作移至protected static方法,以便實作此介面的任何類別可以在他們的實作中重用程式碼。 介面成員的默認實作也會呼叫這個共用方法:

// These methods belong in the ICustomer interface (ICustomer.cs).
public decimal ComputeLoyaltyDiscount() => DefaultLoyaltyDiscount(this);
protected static decimal DefaultLoyaltyDiscount(ICustomer c)
{
    DateTime start = DateTime.Now - length;

    if ((c.DateJoined < start) && (c.PreviousOrders.Count() > orderCount))
    {
        return discountPercent;
    }
    return 0;
}

在實現該介面的類別中,覆寫方法可以呼叫靜態輔助方法,並擴展該邏輯以提供「新客戶」折扣:

// This method belongs in the SampleCustomer class (SampleCustomer.cs) that implements ICustomer.
public decimal ComputeLoyaltyDiscount()
{
   if (PreviousOrders.Any() == false)
        return 0.50m;
    else
        return ICustomer.DefaultLoyaltyDiscount(this);
}

您可以在 GitHub 上的範例存放庫中查看整個已完成的程式代碼。 您可以在 GitHub 上的範例存放庫上取得入門應用程式。

這些新功能表示當這些新成員有合理的默認實作時,介面可以安全地更新。 仔細設計介面,以表達多個類別所實作的單一功能構想。 這可讓您更輕鬆地在發現相同功能概念的新需求時升級這些介面定義。