从 .NET Core 3.0 上的 C# 8.0 开始,可以在声明接口成员时定义实现。 最常见的方案是安全地将成员添加到已经由无数客户端发布并使用的接口。

在本教程中,你将了解:

  • 通过使用实现添加方法,安全地扩展接口。
  • 创建参数化实现以提供更大的灵活性。
  • 使实现器能够以替代的形式提供更具体的实现。
01 系统必备

需要将计算机设置为运行 .NET Core,包括 C# 8.0 预览版编译器。 从 Visual Studio 2019 或最新的 .NET Core 3.0 预览版 SDK 开始,可以使用 C# 8.0 预览版编译器。 从 .NET Core 3.0 预览版 4 开始提供默认接口成员。

02 方案概述
本教程从客户关系库版本 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 接口。 此设计建议引起了经验丰富的开发人员的关注:“一旦发布,接口就是固定不变的! 这是一项突破性的变革!” C# 8.0 添加了默认接口实现 用于升级接口。 库作者可以向接口添加新成员,并为这些成员提供默认实现。

默认接口实现使开发人员能够升级接口,同时仍允许任何实现器替代该实现。 库的用户可以接受默认实现作为非中断性变更。 如果他们的业务规则不同,则可以进行替代。

03 使用默认接口成员升级

团队就最有可能的默认实现达成一致:针对客户的忠实客户折扣。

升级应提供用于设置两个属性的功能:符合折扣条件所需的订单数量以及折扣百分比。 这使其成为用于默认接口成员的完美方案。 可以向 ICustomer 接口添加方法,并提供最有可能的实现。 所有现有的和任何新的实现都可以使用默认实现,或者提供其自己的实现。

首先,将新方法添加到实现中:

// Version 1:
public decimal ComputeLoyaltyDiscount()
{
DateTime TwoYearsAgo = DateTime.Now.AddYears(-);
if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > ))
{
return 0.10m;
}
return ;
}

库作者编写了用于检查实现的第一个测试:

SampleCustomer c = new SampleCustomer("customer one", new DateTime(, , ))
{
Reminders =
{
{ new DateTime(, , ), "childs's birthday" },
{ new DateTime(, , ), "anniversary" }
}
};
SampleOrder o = new SampleOrder(new DateTime(, , ), 5m);
c.AddOrder(o); o = new SampleOrder(new DateTime(, , ), 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

04 提供参数化
这是一个好的开始。 但是,默认实现存在太多限制。 此系统的许多使用者可能会选择不同的购买数量阈值、不同的会员资格时长或不同的折扣百分比。 通过提供用于设置这些参数的方法,可为更多客户提供更好的升级体验。 让我们添加一个静态方法,该方法可设置控制默认实现的三个参数:
// Version 2:
public static void SetLoyaltyThresholds(TimeSpan ago, int minimumOrders = , decimal percentageDiscount = 0.10m)
{
length = ago;
orderCount = minimumOrders;
discountPercent = percentageDiscount;
}
private static TimeSpan length = new TimeSpan( * , ,,); // 2年
private static int orderCount = ;
private static decimal discountPercent = 0.10m; public decimal ComputeLoyaltyDiscount()
{
DateTime start = DateTime.Now - length; if ((DateJoined < start) && (PreviousOrders.Count() > orderCount))
{
return discountPercent;
}
return ;
}

这个小代码片段中展示了许多新的语言功能。 接口现在可以包含静态成员,其中包括字段和方法。 还启用了不同的访问修饰符。 其他字段是专用的,新方法是公共的。 接口成员允许使用任何修饰符。

使用常规公式计算忠实客户折扣但参数有所不同的应用程序不需要提供自定义实现;它们可以通过静态方法设置自变量。 例如,以下代码设置“客户答谢”,奖励任何成为会员超过一个月的客户:

ICustomer.SetLoyaltyThresholds(new TimeSpan(, , , ), , 0.25m);
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
05 扩展默认实现

目前添加的代码提供了方便的实现,可用于用户需要类似默认实现的项目的方案,或用于提供一组不相关的规则。 对于最后一个功能,让我们稍微重构一下代码,以实现用户可能需要基于默认实现进行生成的方案。

假设有一家想要吸引新客户的初创企业。 他们为新客户的第一笔订单提供 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 ;
}

在实现此接口的类的实现中,替代可以调用静态帮助程序方法,并扩展该逻辑以提供“新客户”折扣:

public decimal ComputeLoyaltyDiscount()
{
if (PreviousOrders.Any() == false)
return 0.50m;
else
return ICustomer.DefaultLoyaltyDiscount(this);
}

可以在我们位于 [GitHub 上的示例存储库]中查看整个完成的代码(可以在 GitHub 上的示例存储库中获取入门应用程序)。

这些新功能意味着,当这些新成员拥有合理的默认实现时,接口可以安全地更新。 精心设计接口,以表达可由多个类实现的单个功能概念。 这样一来,在发现针对同一功能概念的新要求时,可以更轻松地升级这些接口定义。

C#8.0 中使用默认接口成员更新接口的更多相关文章

  1. C# 8.0 中开启默认接口实现

    原文:C# 8.0 中开启默认接口实现 当你升级到 C# 8.0 和 .NET Core 3.0 之后,你就可以开始使用默认接口实现的功能了. 从现在开始,你可以在接口里面添加一些默认实现的成员,避免 ...

  2. 在Tomcat7.0中设置默认服务器和不加端口名访问

    前言 昨天买了域名,服务器,然后搭建了环境,然后想他通过默认的端口,不用端口就访问. 设置WEB项目的欢迎页 在WEB-INF文件夹下有个web.xml文件(最近新建的项目不包含此文件,可以手动新建) ...

  3. Springboot2.0中jpa默认创建的mysql表为myisam引擎问题

    使用Springboot2.0后,使用jpa操作mysql数据库时,默认创建的表的引擎是myisam,myisam是不能加外键的,找了一些资源,最终可以用此方法解决! yml格式: spring: j ...

  4. Vue.js2.0中的变化(持续更新中)

    最近自己在学习Vue.js,在看一些课程的时候可能Vue更新太块了导致课程所讲知识和现在Vue的版本不符,从而报错,我会在以后的帖子持续更新Vue的变化与更新,大家也可以一起交流,共同监督学习! 1. ...

  5. IHostingEnvironment VS IHostEnvironment - .NET Core 3.0中的废弃类型

    原文: https://andrewlock.net/ihostingenvironment-vs-ihost-environment-obsolete-types-in-net-core-3/ 作者 ...

  6. asp.net core 3.0 中使用 swagger

    asp.net core 3.0 中使用 swagger Intro 上次更新了 asp.net core 3.0 简单的记录了一下 swagger 的使用,那个项目的 api 比较简单,都是匿名接口 ...

  7. [译]C#8.0中一个使接口更加灵活的新特性-默认接口实现

    9月份的时候,微软宣布正式发布C#8.0,作为.NET Core 3.0发行版的一部分.C#8.0的新特性之一就是默认接口实现.在本文中,我们将一起来聊聊默认接口实现. 众所周知,对现有应用程序的接口 ...

  8. JDK8.0接口中的默认方法和静态方法

    我们在接口中通常定义的方法是抽象方法,即没有方法体,只有返回值类型和方法名:(public abstract) void Method(); 类在实现接口的时候必须重写抽象方法才可以 jdk8中新加的 ...

  9. Java接口成员变量和方法默认修饰符

     Java的interface中,成员变量的默认修饰符为:public static final 所以我们在interface中定义成员变量的时候,可以 1:public static final S ...

随机推荐

  1. Delphi结束进程模块

    function KillTask(ExeFileName: string): integer; const PROCESS_TERMINATE = $0001; var ContinueLoop: ...

  2. 【聚沙成塔系列】之《UML九种图-包图、类图》

    一.包 (一)相关概念: 1.包: 一个包=一层=一个命名空间=一个文件夹 2.包的命名: 简单名:王老二 路径名:中国.河北省.廊坊市.廊坊师范学院.信息技术提高班.九期班.王老 3.可见性: +公 ...

  3. .NET错误:The tag 'BusyIndicator' ('CallMethodAction')does not exist in XML namespace

    将一个项目由VS2010升级为VS2012后,在确保代码无误的情况下编译程序出现以下错误提示: 解决方法:将4.0.0.0的程序集Microsoft.Expression.Interactions.及 ...

  4. 理解call(),apply(),bind()

    三者皆是修改this指向call(this指向,参数,参数,参数...) ====>深研究:https://developer.mozilla.org/zh-CN/docs/Web/JavaSc ...

  5. vue.js异步上传文件前后端代码

    上传文件前端代码如下: <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type&q ...

  6. vue 开发webapp 手机返回键 退出问题

    vue 开发webapp 手机返回键 退出问题 mui进行手机物理键的监听 首先安装 vue-awesome-mui npm i vue-awesome-mui 在main.js注册 在index.h ...

  7. 视频私有云实战:基于Docker构建点播私有云平台

    私有云是为一个客户单独使用而构建的,因而提供对数据.安全性和服务质量的最有效控制.前置条件是客户拥有基础设施,并可以使用基础设施在其上部署应用程序.其核心属性是专有的资源.本篇文章将会结合网易云信的实 ...

  8. JavaScript 操作 DOM 总结

    基本概念 DOM 是 JavaScript 操作网页的接口,全称为"文档对象模型"(Document Object Model).它的作用是将网页转为一个 JavaScript 对 ...

  9. spring 5.x 系列第7篇 —— 整合Redis客户端 Jedis和Redisson (xml配置方式)

    文章目录 一.说明 1.1 Redis 客户端说明 1.2 Redis可视化软件 1.3 项目结构说明 1.3 依赖说明 二.spring 整合 jedis 2.1 新建基本配置文件 2.2 单机配置 ...

  10. 系统学习 Java IO (十)----回退流 PushbackInputStream

    目录:系统学习 Java IO---- 目录,概览 PushbackInputStream 旨在从 InputStream 解析数据时使用. 有时您需要先读取几个字节以查看将要发生的事情,然后才能确定 ...