在设计c#类库时,什么时候应该选择继承而不是接口

本文关键字:继承 选择 接口 什么时候 类库 | 更新日期: 2023-09-27 17:53:20

我有一些Processor类,它们将做两件非常不同的事情,但从通用代码调用("控制反转"情况)。

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

在设计c#类库时,什么时候应该选择继承而不是接口

一般来说,规则是这样的:

  • 继承描述了is-a关系。
  • 实现接口描述了can-do关系。

为了更具体地说明这一点,让我们看一个例子。System.Drawing.Bitmap是一个图像(因此,它继承自Image类),但它也可以做处理,因此它实现了IDisposable接口。它还可以进行序列化,因此它从ISerializable接口实现。

但更实际的是,接口经常被用来模拟c#中的多重继承。如果您的Processor类需要继承System.ComponentModel.Component之类的东西,那么您别无选择,只能实现IProcessor接口。

事实上,接口和抽象基类都提供了一个约定,指定一个特定的类可以做什么。一个常见的误解是,接口是声明契约所必需的,但这是不正确的。在我看来,最大的优点是抽象基类允许您为子类提供默认功能。但是如果没有有意义的默认功能,就没有什么可以阻止您将方法本身标记为abstract,要求派生类自己实现它,就像它们要实现接口一样。

对于这类问题的答案,我经常求助于。net框架设计指南,它对类和接口之间的选择有如下说明:

一般来说,类是公开抽象的首选构造。

接口的主要缺点是,当涉及到允许api的发展时,它们远不如类灵活。一旦你发布了一个接口,它的成员集就永远是固定的。对接口的任何添加都将破坏实现该接口的现有类型。

类提供了更多的灵活性。您可以向已经交付的类添加成员。只要该方法不是抽象的(也就是说,只要你提供了该方法的默认实现),任何现有的派生类都将继续保持不变。

[…])

支持接口的一个最常见的论点是,接口允许将契约从实现中分离出来。但是,该参数错误地假设不能使用类将契约与实现分离。从具体实现中分离出来的抽象类是实现这种分离的好方法。

他们的一般建议如下:

  • Do倾向于定义类而不是接口。
  • Do使用抽象类而不是接口来将契约与实现解耦。如果定义正确,抽象类允许在契约和实现之间实现相同程度的解耦。
  • 如果需要提供值类型的多态层次结构,则定义接口。
  • 考虑定义接口来实现类似于多重继承的效果。

Chris Anderson特别赞同最后一条原则,他认为:

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

Richard

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

这样,如果你将来需要一个IProcessor,而不是BaseProcessor的服务,它不必有它(可能隐藏它)…但那些想要的人可以拥有它……减少重复的代码/概念。

这只是我的拙见。

欢呼。基斯。

接口是一个"契约",它们确保某些类实现所需的一组成员——属性、方法和事件。

基类(具体或抽象,无关紧要)是某些实体的原型。也就是说,这些实体代表了某些实际物理或概念上的共同之处。

何时使用接口?

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

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

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


例子:

让我们来谈谈表

  • 我们接受可回收的表 => 这必须用一个像" irecyabletable "这样的接口来定义,以确保所有可回收的表都将有一个"Recycle"方法

  • 我们想要桌面表 => 这必须用继承来定义。"桌面桌"就是"桌子"。所有的表都有共同的属性和行为,桌面表将有相同的属性和行为,添加这样的东西,使桌面表的工作方式不同于其他类型的表

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

我不太擅长设计选择,但如果被问到,如果只有成员要扩展,我宁愿实现iProcessor接口。如果有其他不需要扩展的函数,从baseprocessor继承是更好的选择。

不考虑设计的纯粹性,你只能继承一次,所以如果你需要继承一些框架类,这个问题就没有意义了。

如果有选择的话,实际上你可以选择节省最多输入的选项。这通常是最纯粹的选择。

编辑:

如果基类将有一些实现,那么这可能是有用的,如果它是纯抽象的,那么它也可能是一个接口。

考虑到SOLID原则为您的项目提供了更多的可维护性和可扩展性,我更喜欢接口而不是继承。

同样,如果你需要添加"额外的功能"到你的接口,最好的选择是创建一个新的接口,遵循I在SOLID中,这是接口隔离原则。

无论你选择什么都不重要,总是选择接口。它允许更多的灵活性。它可以保护您免受将来的更改(如果您需要更改基类中的某些内容,继承的类可能会受到影响)。它还允许更好地封装细节。如果你正在使用依赖注入的控制反转,他们倾向于接口。

或者如果无法避免继承,那么将它们同时使用可能是个好主意。创建实现接口的抽象基类。在你的例子中,ProcessorBase实现了一个IProcessor。

类似于ASP中的ControllerBase和IController。净Mvc。