C# 8 默认实现和依赖倒置

C# 8 Default implementation and Dependency Inversion

Microsoft 在 C#8 中引入了接口方法的默认实现。它仍然是一个相当新的功能,似乎有很多关注此问题的博客作者。

我想知道默认实现是否有可能成为依赖倒置和 DI 的有用工具,或者它是否会促进不良的编程风格?它是否违反了 SOLID 等众所周知的原则?

默认接口实现有两个主要的设计目标。更重要的是一直追溯到有关设计界面的指南。特别是,一旦您发布了一个界面,它就应该一成不变并且永远不会改变。问题是,这也是一条被忽略的规则……一直以来。

第一个也是主要的实用程序是默认接口实现允许您将新成员引入接口,而不会破坏与该 (public) 接口使用者的源代码或二进制兼容性。这仍然限制了您在更改 public 界面时可以进行的更改类型,但也使客户更容易使用新界面 - 升级是免费的,他们可以立即开始使用新功能。

第二个设计目标是用 特征 扩展 classes - 这在游戏开发中早已被使用。基本思想是,您可以通过让 class 实现一个接口来向 class 添加新的明确定义的行为,同时还保留修改 class 本身行为的能力。这本质上是一种相对较弱的元编程形式。

当然,仅仅因为这些是设计目标并不意味着它们是您应该使用默认实现的唯一方式。但是如果你概括一下,你会得到这两个基本用途:

  • 在不破坏兼容性且不必对接口进行版本控制的情况下扩展接口。
  • 扩展 classes 而不必创建从 class 继承中获得的严格树层次结构。

事实上,您甚至可以说这比 class 继承更简单、更清晰、更强大。在某种程度上,这是从扩展方法开始的方法的延续——本质上,默认接口方法实现是一个也是虚拟的扩展方法。默认实现 只能 使用 public 接口,但实现 class 也可以使用自己的隐藏状态。它为 C# 提供了一种有限形式的多重继承,而无需处理两个 "parents" 的状态如何连接在一起(因为接口没有任何状态)。

最后,如果您担心像 SOLID 这样的原则,让我们试一试:

  • SRP - 没有真正的变化,有几个新选项可以在一个地方非破坏性地添加新逻辑。我说这是 SRP 的小胜利。
  • OCP - 你可以获得强大的扩展 classes 的新方法,而无需修改 class 本身,只要 class 实现了新功能有意义的接口.但是你不能改变现有的classes行为,所以它仍然关闭以供修改。在这里获胜非常有用。
  • LSP - 没有真正受到影响。允许您仅通过实现接口(可以根据需要覆盖该行为的选项)向 class 添加新的可替代行为的一些小胜利。
  • ISP - 这可以双向进行。如果您有合理的默认实现,则默认实现可能会使拥有许多小接口变得更容易。但它们也可以鼓励您继续修改现有界面,而不是添加新界面。
  • DIP - 基本不受影响。如果新抽象是对现有内容的合理扩展,或者可以在不依赖状态的情况下实现默认行为,则可以更轻松地添加新抽象。您也可能会想使用默认行为作为契约,但在我看来,它仍然比抽象方法(您还可以控制状态)更容易受到诱惑。