教程:使用C#8.0中的默认接口成员更新接口

从.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开始,默认接口成员可用。


方案概述


本教程从客户关系库的版本1开始。 您可以在GitHub示例存储库中获取starter应用程序。 构建该库的公司旨在让客户使用现有的应用程序来采用他们的库,他们为库的用户提供了最少的接口定义以供实施,这是客户的接口定义:


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); // 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


提供参数化


这是一个好的开始。 但是,默认实现过于严格。 该系统的许多消费者可能会选择不同的购买门槛,不同的会员资格长度或不同的折扣率,通过提供一种设置这些参数的方法,可以为更多客户提供更好的升级体验。 让我们添加一个静态方法,该方法设置这三个参数来控制默认实现:


 // 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方法中,以便任何实现此接口的类都可以在其实现中重用代码。 接口成员的默认实现也调用此共享方法:


 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上的示例存储库中获取starter应用程序。


这些新功能意味着,对于那些新成员有合理的默认实现时,可以安全地更新接口。 精心设计接口以表达可以由多个类实现的单个功能思想。 当发现相同功能思想的新需求时,这将使升级这些接口定义更加容易。

Source: https://habr.com/ru/post/zh-CN456238/


All Articles