從 .NET Core 3.0 上的 C# 8.0 開始,可以在聲明接口成員時定義實現。 最常見的方案是安全地將成員添加到已經由無數客戶端發布並使用的接口。
在本教程中,你將了解:
- 通過使用實現添加方法,安全地擴展接口。
- 創建參數化實現以提供更大的靈活性。
- 使實現器能夠以替代的形式提供更具體的實現。
需要將計算機設置為運行 .NET Core,包括 C# 8.0 預覽版編譯器。 從 Visual Studio 2019 或最新的 .NET Core 3.0 預覽版 SDK 開始,可以使用 C# 8.0 預覽版編譯器。 從 .NET Core 3.0 預覽版 4 開始提供默認接口成員。
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
接口。 此設計建議引起了經驗豐富的開發人員的關注:“一旦發布,接口就是固定不變的! 這是一項突破性的變革!” C# 8.0 添加了默認接口實現 用於升級接口。 庫作者可以向接口添加新成員,並為這些成員提供默認實現。
默認接口實現使開發人員能夠升級接口,同時仍允許任何實現器替代該實現。 庫的用戶可以接受默認實現作為非中斷性變更。 如果他們的業務規則不同,則可以進行替代。
團隊就最有可能的默認實現達成一致:針對客戶的忠實客戶折扣。
升級應提供用於設置兩個屬性的功能:符合折扣條件所需的訂單數量以及折扣百分比。 這使其成為用於默認接口成員的完美方案。 可以向 ICustomer 接口添加方法,並提供最有可能的實現。 所有現有的和任何新的實現都可以使用默認實現,或者提供其自己的實現。
首先,將新方法添加到實現中:
// Version 1: public decimal ComputeLoyaltyDiscount() { DateTime TwoYearsAgo = DateTime.Now.AddYears(-2); if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > 10)) { return 0.10m; } return 0; }
庫作者編寫了用於檢查實現的第一個測試:
SampleCustomer c = new SampleCustomer("customer one", new DateTime(2010, 5, 31)) { Reminders = { { new DateTime(2010, 08, 12), "childs's birthday" }, { new DateTime(1012, 11, 15), "anniversary" } } };
SampleOrder o = new SampleOrder(new DateTime(2012, 6, 1), 5m); c.AddOrder(o); o = new SampleOrder(new DateTime(2103, 7, 4), 25m); c.AddOrder(o); // 檢查折扣 ICustomer theCustomer = c; Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
注意測試的以下部分:
// 檢查折扣 ICustomer theCustomer = c; Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
從 SampleCustomer
到 ICustomer
的強制轉換是必需的。 SampleCustomer
類不需要為 ComputeLoyaltyDiscount
提供實現;這由 ICustomer
接口提供。 但是,SampleCustomer
類不會從其接口繼承成員。 該規則沒有更改。 若要調用在接口中聲明和實現的任何方法,該變量的類型必須是接口的類型,在本示例中為 ICustomer
。
// 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); // 2年 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
方法,以便實現此接口的任何類都可以在其實現中重用代碼。 接口成員的默認實現也調用此共享方法:
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; }
在實現此接口的類的實現中,替代可以調用靜態幫助程序方法,並擴展該邏輯以提供“新客戶”折扣:
public decimal ComputeLoyaltyDiscount() { if (PreviousOrders.Any() == false) return 0.50m; else return ICustomer.DefaultLoyaltyDiscount(this); }
可以在我們位於 [GitHub 上的示例存儲庫]中查看整個完成的代碼(可以在 GitHub 上的示例存儲庫中獲取入門應用程序)。
這些新功能意味着,當這些新成員擁有合理的默認實現時,接口可以安全地更新。 精心設計接口,以表達可由多個類實現的單個功能概念。 這樣一來,在發現針對同一功能概念的新要求時,可以更輕松地升級這些接口定義。