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

When should I choose inheritance over an interface when designing C# class libraries?

我有许多Processor类,它们可以做两个非常不同的事情,但是它们是从公共代码(控制反转)情况)调用的。

我想知道,在决定它们是否都应该从BaseProcessor继承,或将IProcessor实现为接口时,我应该认识到(或认识到,对你们这些苏联人来说)什么样的设计考虑。


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

  • 继承描述的是IS-A关系。
  • 实现接口描述了一个可以做的关系。

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

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

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

对于这样的问题的答案,我经常求助于.NET框架设计指南,其中关于在类和接口之间选择的内容如下:

In general, classes are the preferred construct for exposing abstractions.

The main drawback of interfaces is that they are much less flexible than classes when it comes to allowing for the evolution of APIs. Once you ship an interface, the set of its members is fixed forever. Any additions to the interface would break existing types implementing the interface.

A class offers much more flexibility. You can add members to classes that you have already shipped. As long as the method is not abstract (i.e., as long as you provide a default implementation of the method), any existing derived classes continue to function unchanged.

[ . . . ]

One of the most common arguments in favor of interfaces is that they allow separating contract from the implementation. However, the argument incorrectly assumes that you cannot separate contracts from implementation using classes. Abstract classes residing in a separate assembly from their concrete implementations are a great way to achieve such separation.

其一般建议如下:

  • 赞成在接口上定义类。
  • 一定要使用抽象类而不是接口将契约与实现分离。如果定义正确,抽象类允许在契约和实现之间实现相同程度的分离。
  • 如果需要提供值类型的多态层次结构,请定义接口。
  • 考虑定义接口以实现与多重继承类似的效果。

Chris Anderson对最后一条原则表示特别同意,他认为:

Abstract types do version much better, and allow for future extensibility, but they also burn your one and only base type. Interfaces are appropriate when you are really defining a contract between two objects that is invariant over time. Abstract base types are better for defining a common base for a family of types.


理查德,

为什么要在它们之间选择?我将拥有一个作为已发布类型的iprocessor接口(在系统中的其他地方使用);如果您当前的各种iprocessor实现具有公共行为,那么抽象的baseprocessor类将是实现该公共行为的真正好地方。

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

只是我的拙见。

干杯。基思。


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

基类(具体的或抽象的,不重要)是某些实体的原型。也就是说,这些实体表示一些实际的物理或概念上的实体中常见的东西。

何时使用接口?

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

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

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

示例:

我们来谈谈桌子吧。

  • 我们接受可回收表=>这必须通过类似"IRecyclableTable"的接口来定义,以确保所有可回收表都具有"Recycle"方法。

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

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


撇开设计的纯粹性不谈,您只能继承一次这样,如果您需要继承一些框架类,那么问题就没有了。

如果您有选择,务实地说,您可以选择保存最多输入的选项。无论如何,这通常是纯粹主义的选择。

编辑:

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


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


如果您选择的内容不重要,请始终选择Interface。它允许更多的灵活性。它可能保护您不受将来更改的影响(如果您需要更改基类中的某些内容,继承的类可能会受到影响)。它还允许更好地封装细节。如果您使用依赖注入的控制反转,那么它们倾向于使用接口。

或者,如果继承无法避免,那么将它们一起使用可能是个好主意。创建实现接口的抽象基类。在您的情况下,processorbase实现一个iprocessor。

类似于ASP.NET MVC中的ControllerBase和IController。


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

此外,如果您需要向接口添加"附加功能",最好的选择是按照I in solid(接口隔离原则)创建一个新的接口。