30

接口+扩展方法(mixin)比抽象类更可取吗?

如果您的答案是“取决于”,它取决于什么?

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

  • 接口是可多重继承的,而类则不是。
  • 您可以使用扩展方法以非破坏方式扩展接口。(实现您的接口的客户端将获得您的新基本实现,但仍然能够覆盖它。)

我还没有想到这种方法的缺点。接口+扩展方法失败的原因可能非常简单。

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

4

3 回答 3

25

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

就我而言,就是这样——我认为基于接口的方法要好得多。然后,您可以很好地模拟出您的依赖关系,并且所有内容基本上都没有那么紧密耦合。我不是类继承的忠实拥护者,除非它真的是关于专业化的:)

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

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

于 2009-04-23T20:09:17.323 回答
23

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

您应该将一切用于其设计目的。

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

  • 抽象基类确实经常被误用于“重用代码”(而不是真正的继承)。这通常适用于继承。

问题应该是:“我什么时候应该使用接口、扩展方法或基类?”

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

编辑:

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

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

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

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

于 2009-04-23T22:24:35.410 回答
1

接口倾向于使代码更简洁我觉得它更容易测试。当您添加扩展时,您会增加更多的灵活性,同时保持干净的可测试代码。

对我来说,抽象类总是显得笨重,使用接口我可以拥有一个对象工厂,它返回一个特定于我想要完成的对象(关注点分离)。

只是编造一些东西 A 有一个名为 Math 的接口,它具有加、减、除和乘法,然后我有一个名为 IntMAth 的类,它实现了针对整数数学优化的 Math,我还有另一个名为 FloatMath 的类,它实现了优化的 Math对于浮动数学,我有一个通用数学,它实现了处理其他所有事情的数学。

当我需要添加一些浮点数时,我可以调用我的工厂 MathFactory.getMath(typeof(float)) 并且它有一些逻辑可以知道如果我传入的类型是浮点数,那么它会返回 FloatMath 类。

这样我所有的类都更小更易于维护,调用类的代码更小,等等。

于 2009-04-23T21:19:19.683 回答