在设计C#类库时,何时应该在接口上选择继承?

pro*_*ach 67 c# oop inheritance design-patterns interface

我有一些数字Processor类可以完成两个非常不同的事情,但是从公共代码调用("控制反转"情况).

我想知道在决定它们是否应该全部继承BaseProcessor或实现IProcessor为接口时,我应该认识(或认识到,对于你的USsers)的设计考虑因素.

Cod*_*ray 166

通常,规则如下:

  • 继承描述了一种is-a关系.
  • 实现接口描述了一种可以做的关系.

更具体地说,让我们看一个例子.的System.Drawing.Bitmap是-一个图像(并且同样地,其从继承Image类),但它也可以-DO配置,所以它实现了IDisposable接口.它也可以进行序列化,因此它从ISerializable界面实现.

但实际上,接口通常用于模拟C#中的多重继承.如果你的Processor类需要继承类似的东西System.ComponentModel.Component,那么你别无选择,只能实现一个IProcessor接口.

事实是,接口和抽象基类都提供了一个指定特定类可以执行的操作的契约.这是一个常见的神话,接口是宣布这个合同所必需的,但这是不正确的.我认为最大的优点是抽象基类允许您为子类提供默认功能.但是如果没有合理的默认功能,那么就没有什么可以阻止你将方法本身标记为abstract,要求派生类自己实现它,就像它们实现接口一样.

对于这样的问题的答案,我经常转向.NET Framework设计指南,它有关于在类和接口之间进行选择的说法:

通常,类是暴露抽象的首选构造.

接口的主要缺点是,当允许API的发展时,它们比类更不灵活.一旦发布了接口,其成员集将永久固定.对接口的任何添加都会破坏实现接口的现有类型.

课程提供了更大的灵活性.您可以将成员添加到已发布的类中.只要该方法不是抽象的(即,只要您提供该方法的默认实现),任何现有的派生类都将继续保持不变.

[...]

支持接口的最常见的一个论点是它们允许将契约与实现分开.但是,该参数错误地假定您不能使用类将实现与实现分开.驻留在与其具体实现不同的单独程序集中的抽象类是实现这种分离的好方法.

他们的一般建议如下:

  • 不要青睐定义了接口的类.
  • 不要使用,而不是接口抽象类解耦实现合同.如果正确定义抽象类,则允许在合同和实现之间进行相同程度的解耦.
  • 不要,如果你需要提供一个值类型的多态层次定义一个接口.
  • 考虑定义接口以实现与多重继承类似的效果.

克里斯安德森对这最后的宗旨表示特别赞同,认为:

抽象类型的版本更好,并允许将来的可扩展性,但它们也会烧掉你唯一的基类型.当您真正定义两个随时间不变的对象之间的契约时,接口是合适的.抽象基类型更适合为一类类型定义公共基础.

  • 应该注意的是,界面更像是"必须做". (13认同)

cor*_*ttk 9

理查德,

为什么选择他们之间?我有一个IProcessor接口作为发布类型(用于系统中的其他地方); 如果碰巧你的各种各样的IProcessor实现具有共同行为,那么抽象的BaseProcessor类将是实现这种常见行为的真正好地方.

这样,如果您将来需要一个没有用于BaseProcessor服务的IProcessor,它就没有它(并且可能隐藏它)......但是那些想要它的人可以拥有它...减少它在重复的代码/概念中.

只是我的拙见.

干杯.基思.


Mat*_*zer 5

接口是一个"契约",它们确保某些类实现了一组理想的成员 - 属性,方法和事件.

基类(具体或抽象,无关紧要)是某个实体的原型.这些是代表一些实际物理或概念中常见的实体.

什么时候使用接口?

每当某种类型需要声明时,至少会有一些消费者应该关心的行为和属性,并使用它们来完成某些任务.

何时使用基类(具体和/或抽象)

每当一组实体共享相同的原型时,意味着B继承A,因为B是具有差异的A,但是B可以被识别为A.


例子:

我们来谈谈桌子.

  • 我们接受可循环使用的表 => 必须使用类似"IRecyclableTable"的接口来定义它,以确保所有可循环使用的表都具有"循环"方法.

  • 我们想要桌面桌 => 这必须用继承来定义."桌面桌"是"桌子".所有表都具有共同的属性和行为,而桌面的表将添加相同的表,使桌面表的工作与其他类型的表不同.

我可以在一个对象图中谈论关联,这两种情况的意义,但在我看来,如果我需要从概念的角度来论证,我会用这个论证来回答.

  • 我不太确定什么是“可回收表”,但我可能会实现一个`IRecyclable` 接口,因为似乎有几个*不相关*的对象可能需要是可回收的(即,其他不代表表的类) . 然后我仍然会从`Table` 类实现所有的表,包括`DesktopTable` 和`PicnicTable` 和`FoldingTable`。 (2认同)