Interface + Extension (mixin) vs Base Class
接口+扩展方法(mixin)比抽象类更可取吗?
如果你的答案是"它取决于",它取决于什么?
我看到了接口+扩展方法的两个可能优势。
- 接口是多重可继承的,类不是。
- 可以使用扩展方法以非中断方式扩展接口。(实现接口的客户机将获得新的基本实现,但仍然能够覆盖它。)
我还没有想到这种方法会有什么缺点。接口+扩展方法失败的原因可能非常简单。
关于这个主题的两篇有用的文章是
- 使用接口和扩展方法创建混合函数
- 抽象基类也有版本控制问题
- 我还没有看到一个明确的答案。到目前为止,我认为乔恩的回答是最有帮助的,但一定要阅读斯特凡·斯坦内格的回答。它们都提出了在选择方法之前应该考虑的重要要点。我现在的理解是"需要使用继承来覆盖方法吗?使用抽象基。否则使用接口+扩展。"
扩展方法的缺点:pre-c 3/vb9客户机将无法如此轻松地使用它。
就我而言,这就是问题所在——我认为基于接口的方法明显更好。然后您可以很好地模拟您的依赖关系,基本上一切都不那么紧密地耦合在一起。我不太喜欢类继承,除非它是专门化的。)
编辑:我刚刚想到了另一个可能相关的好处。一些具体的实现可能提供一些通用方法的更优化版本。
Enumerable.Count就是一个很好的例子,它明确地检查序列是否实现IList,因为如果实现,它可以在列表中调用Count,而不是遍历整个序列。如果IEnumerable是一个带有虚拟Count()方法的抽象类,它可能在List中被重写,而不是有一个明确知道IList的单一实现。我不是说这总是相关的,也不是说IEnumerable应该是一个抽象类(绝对不是!)-只是指出它可能是一个小的缺点。这就是多态性真正适合的地方,通过专门化现有的行为(公认的是,这种方式只影响性能而不是结果)。
- 谢谢,乔恩。"具体的实现可以提供更优化的版本,"具体的版本不能在这两种方法中都这样做吗?"如果扩展方法与"msdn.microsoft.com/en-us/library/bb383977.aspx"类型中定义的方法具有相同的签名,则永远不会调用它。听起来,您可以无论如何重写具体类中的扩展方法。我想如果客户机将其强制转换为IBase,那么它不会,但抽象类方法也是这样,不是吗?
- 不,你不能真的覆盖它们。您可以提供具有相同签名的方法,然后如果调用者知道这些方法,就可以调用它们——但不会根据执行时间类型以多态方式调用它们。
- 如果扩展方法中有逻辑,则不能模拟出来。当然,您模拟了接口,但是静态扩展方法仍然存在。它不是接口的成员。因此,您并不真正使用接口,而是使用静态方法。我并不是说扩展方法不好,但您关于耦合的论点相当薄弱。
- 模仿界面正是我所说的。是的,最终会在某种程度上耦合到静态方法实现(尽管您可以编写测试助手,有效地对模拟执行相同的操作,提供指定的结果,以便将其封装在一个地方)。您仍然将调用者从实际的代码中分离出来,而实际的代码应该在具体的派生类中。是的,你可以模仿抽象类——但是IME比接口更糟糕。
- 接口上的泛型扩展方法也完全有可能接受一个约束类型参数,然后将其传递给其他人。阿法克,这是不可能做到这一点的任何其他手段(有这么多类型的安全和松耦合)。
- @7年后你对这件事有何感想?我一直坚持做事情的"老方法",因为对于普通开发人员来说,这可能更容易理解。自2009年以来,你是否学到了改变你观点的东西,或者C增加了什么?
- @DSS539:我仍然很少使用抽象类…但就像所有的事情一样,基本上是"视情况而定"。最近版本的C中没有什么特别影响这一点,我现在可以想到。
嗯,这是个错误的问题。
你应该把一切都用在它的设计上。
问题应该是:"什么时候应该使用接口、扩展方法或基类?"
- 在需要合同时使用接口(这种情况一直都会发生)。
- 当您有实际继承的情况时使用(抽象的)基类(您可以编写一本关于如何判断的书,所以我就这样离开它)。一个接口也大多是同时实现的。
- 对不应实际是类型成员的逻辑使用扩展方法,因为实现它不是类型的责任-但您希望使其易于查找,并且像成员一样调用它感觉很自然。
编辑:
或者问题应该是:"如何编写不属于基类的可重用功能?"
- 编写一个公开功能的接口
- 编写一个实现功能的可重用库类
- 编写一个实现接口的类,并通过聚合可重用类来重用功能。
通常我会说,扩展方法对于业务逻辑来说是错误的地方,除了特殊情况或特殊设计决策。
只有在少数情况下,基类才是正确的决定。但事实并非如此。毫无疑问,你应该重新考虑一下。
- 扩展方法不是真正的封装吗?也许我不明白,但它是如何打破封装的?您是说扩展方法阻止对象具有私有的状态信息吗?在任何情况下,关于何时使用接口vs基类的建议都与krzyzstof cwalina的建议非常相反。en.csharp online.net/…。
- 你关于"按设计使用某物"的观点很重要,因为按设计使用某物通常会提高可读性。+1然而,以设计师不理解的方式使用某些东西实际上可能比按他们的预期方式使用要好(在某些情况下,不是全部)。
- 这篇文章有点奇怪。有一些API只基于接口,如NHibernate和许多其他接口。它使它非常可测试(这是MS不太了解的事情),并将您的域与实现您使用的某些功能的类分离开来。""总是针对接口实现"是许多人遵循的一个准则。-对于.NET框架类,它可能是不同的,但我不知道为什么它在那里应该是不同的。
- @DSS539,关于封装的第一个问题:扩展方法只是类的外部。扩展方法无法访问私有状态。它们"破坏"封装,只有当您被迫公开那些实际上应该是私有的东西时,因为扩展方法需要访问它。
接口会使代码更干净一些,我觉得测试起来更容易一些。当您添加扩展时,在保持干净的可测试代码的同时添加了更多的灵活性。
对于我来说,抽象类总是显得笨重,使用接口,我可以拥有一个对象工厂,它返回一个特定于我要完成的工作(关注点分离)的对象。
只是在编些东西a有一个名为math的接口,它有加法、减法、除法和乘法,然后我有一个名为intmath的类,它实现了为整数数学而优化的数学,还有一个名为floatmath的类,它实现了为浮动数学而优化的数学,我有一个通用数学,它实现了处理其他一切的数学。
当我需要添加一些float时,我可以调用我的工厂mathfactory.getmath(typeof(float)),它有一些逻辑可以知道,如果我传递的类型是float,那么它将返回floatmath类。
这样,我所有的类都更小,更易于维护,调用类的代码更小,等等。
- 我认为我将如何进行课堂数学。避免了工厂的需要。
- 在这种情况下,我会同意,但这只是一个我在飞行中想到的例子。