接口+扩展(mixin)与基类

dss*_*539 29 .net extension-methods abstract-class interface

接口+扩展方法(mixin)是否优于抽象类?

如果你的答案是"它取决于",它依赖于什么?

我看到接口+扩展方法有两个可能的优点.

  • 接口是多重可继承的,而类则不是.
  • 您可以使用扩展方法以不间断的方式扩展接口.(实现您的界面的客户端将获得新的基本实现,但仍然可以覆盖它.)

我还没有想到这种方法的缺点.接口+扩展方法失败可能有一个明显的简单原因.

关于这个主题的两篇有用的文章是

Jon*_*eet 24

扩展方法的缺点:C#3/VB9之前的客户端将无法轻松使用它.

就我而言,这就是它 - 我认为基于接口的方法非常好.然后,您可以很好地模拟您的依赖关系,并且所有内容基本上都不那么紧密耦合.我不是类继承的忠实粉丝,除非它真的是专业化:)

编辑:我刚刚想到了另一个可能相关的好处.某些具体实现可能会提供某些常规方法的更优化版本.

Enumerable.Count这是一个很好的例子 - 它显式检查序列是否实现IList,因为如果它实现,它可以调用Count列表而不是遍历整个序列.如果IEnumerable<T>是一个带有虚Count()方法的抽象类,它可能已被覆盖,List<T>而不是只有一个IList明确知道的实现.我不是说这总是相关的,也不IEnumerable<T>应该是一个抽象类(绝对不是!) - 只是指出它是一个小的可能的缺点.通过专门化现有行为(确实只会影响性能而不是结果),这就是多态性真正合适的地方.

  • 不,你不能真正覆盖它们.您可以提供具有相同签名的方法,然后它们将被调用*如果*调用者知道它们 - 但它们不会基于执行时类型被多态*调用*. (3认同)

Ste*_*ger 23

恕我直言,这是错误的问题.

您应该将所有内容用于其设计目标.

  • 扩展方法不是成员.它们在语法上看起来像成员,所以你可以更容易地找到它们.
  • 扩展方法只能使用公共(或内部)接口.许多其他类也可以这样做.因此,扩展方法不是一种真正的封装方式.
  • 它们是静态方法,不能被覆盖,也不能在单元测试中被模拟.是非OO语言功能,并且调用者静态绑定它.

  • 抽象基类实际上经常被误用于"重用代码"(而不是真正的继承).这通常适用于继承.

问题应该是:"何时应该使用接口,扩展方法或基类?"

  • 在需要合同时使用接口(这种情况一直发生).
  • 当你有真正的继承情况时使用(抽象)基类(你可以写一本关于如何判断它的书,所以我就这样离开它).接口大多数也是同时实现的.
  • 使用不应该实际上是该类型成员的逻辑的扩展方法,因为它不是实现它的类型的责任 - 但是你想让它易于查找,并且把它称为成员就很自然了.

编辑:

或者问题应该是:"如何编写不属于基类的可重用功能?"

  • 编写一个公开功能的接口
  • 编写一个可重用的库类来实现该功能
  • 编写一个实现接口的类,并通过聚合可重用的类来重用该功能.

总的来说,我会说,除特殊情况或特殊设计决策外,扩展方法是业务逻辑的错误位置.

基类只在极少数情况下才是正确的决定.有疑问,事实并非如此.毫无疑问,你应该再考虑一下.

  • @ dss539,关于封装的第一个问题:扩展方法就在类之外.扩展方法无法访问私有状态.只有当你被迫公开实际上应该是私有的,因为扩展方法需要访问它时,它们才会"破坏"封装. (4认同)