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

时间:2011-04-28 09:37:07

标签: c# oop design-patterns inheritance interface

我有一些Processor个类会做两个非常不同的事情,但会从公共代码调用(“控制反转”情况)。

我想知道在决定是否应该从BaseProcessor继承,或者实现IProcessor作为接口时,我应该认识到(或者认识到你的USsers)的设计考虑因素。

7 个答案:

答案 0 :(得分:156)

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

  • 继承描述是-a 关系。
  • 实施界面描述 can-do 关系。

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

但实际上,接口通常用于模拟C#中的多重继承。如果您的Processor类需要继承System.ComponentModel.Component之类的内容,那么您别无选择,只能实现IProcessor接口。

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

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

  

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

     

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

     

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

     

[。 。 。 ]

     

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

他们的一般建议如下:

  • 支持支持在接口上定义类。
  • 执行使用抽象类而不是接口来将合同与实现分离。如果正确定义抽象类,则允许在合同和实现之间进行相同程度的解耦。
  • 如果您需要提供值类型的多态层次结构,请定义接口。
  • 考虑定义接口以实现与多重继承类似的效果。

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

  

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

答案 1 :(得分:8)

理查德,

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

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

我的拙见。

干杯。基思。

答案 2 :(得分:4)

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

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

何时使用界面?

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

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

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


示例:

我们来谈谈表格。

  • 我们接受可回收的表格 => 必须使用“IRecyclableTable”之类的接口来定义,以确保所有可循环使用的表格都具有“回收”方法

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

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

除了设计纯度之外,你只能继承一次,如果你需要继承一些框架类,问题就没那么了。

如果您有实际选择,可以选择保存最多输入的选项。无论如何,它通常是纯粹的选择。

编辑:

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

答案 5 :(得分:0)

如果您选择的任何内容无关紧要,请始终选择界面。它允许更多的灵活性。它可以保护您免受未来的更改(如果您需要更改基类中的某些内容,则可能会影响继承的类)。它还允许更好地封装细节。如果您正在使用一些依赖注入的控制反转,他们倾向于支持接口。

或者如果无法避免继承,可能最好同时使用它们。创建实现接口的抽象基类。在您的情况下,ProcessorBase实现了一个IProcessor。

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

答案 6 :(得分:-2)

鉴于SOLID原则为您的项目提供了更多的可维护性和可扩展性,我宁愿接口而不是继承。

此外,如果您需要在界面中添加“其他功能”,最好的选择是按照SOLID中的I(即接口隔离原则)创建一个新的界面。