何时使用接口或抽象类? 何时使用?

虽然某些指导方针声明,当您想要为inheritance不清楚的类定义合同( IDomesticated ),并且在类是另一个类的扩展时( Cat : MammalSnake : Reptile )的inheritance,您应该使用接口。 (在我看来)这些指导方针进入灰色地带的情况。

例如,说我的实施是Cat : PetPet是一个抽象的类。 是否应该扩展到Cat : Mammal, IDomesticated Mammal是抽象类和IDomesticated是一个接口? 还是我和KISS / YAGNI的原则相冲突(即使我不确定将来会不会有一个Wolf阶级,而这个阶级是不能从Petinheritance的)?

摆脱隐喻的CatPet ,让我们说我有一些代表传入数据来源的类。 他们都需要以某种方式实施相同的基地。 我可以在一个抽象的Source类中实现一些通用的代码,并inheritance它。 我也可以创build一个ISource接口(对我来说感觉更“正确”),并在每个类中重新实现generics代码(这不太直观)。 最后,我可以通过制作抽象类和接口来“吃蛋糕”。 什么是最好的?

这两种情况提出只使用抽象类,只使用一个接口,同时使用抽象类和接口。 这些都是有效的select,还是有什么“规则”,什么时候应该用于另一个?


我想澄清的是,通过“同时使用抽象类和接口”来包含它们本质上代表相同事物( SourceISource都具有相同成员)的情况,但是该类在接口指定时添加了一般function合同。

另外值得注意的是,这个问题主要针对不支持多inheritance的语言(如.NET和Java)。

作为第一个经验法则,我比较喜欢基于.NETdevise指南的抽象类。 推理应用比.NET更广泛,但在“ 框架devise指南”中有更好的解释。

抽象基类偏好背后的主要原因是版本控制,因为您可以始终将新的虚拟成员添加到抽象基类,而不会破坏现有客户端。 这是不可能的接口。

有一些情况下界面仍然是正确的select(特别是当你不关心版本控制时),但是意识到优点和缺点使你能够做出正确的决定。

所以作为我继续之前的部分答案:如果您决定首先对接口进行编码,那么同时拥有一个接口和一个基类是有意义的。 如果你允许一个接口,你必须只针对这个接口进行编码,否则你会违反Liskovreplace原则。 换句话说,即使你提供了一个实现接口的基类,你也不能让你的代码使用这个基类。

如果你决定对一个基类进行编码,那么拥有一个接口是没有意义的。

如果您决定针对接口进行编码,则提供默认function的基类是可选的。 这是没有必要的,但可能会加速实施者的事情,所以你可以提供一个作为礼貌。

想到的一个例子就是ASP.NET MVC。 请求pipe道在IController上工作,但有一个Controller基类,通常用于实现行为。

最终答案:如果使用抽象基类,只使用它。 如果使用接口,基类对实现者是可选的。


更新:不再喜欢抽象类比接口,我也没有很长一段时间; 相反,我赞成使用SOLID作为指导,而不是inheritance。

(虽然我可以直接编辑上面的文本,但是会彻底改变post的性质,而且由于less数人已经发现它有足够的价值,所以我宁愿让原文站起来,而是加上这个请注意,后面的这个post还是有意义的,所以删除它也是一种遗憾。)

我倾向于使用基类(抽象与否)来描述什么是事物,而我使用接口来描述对象的能力

是一种哺乳动物,但它的一个能力是Pettable。

或者,换一种说法,类是名词,而接口映射更接近形容词。

从MSDN, 抽象类与接口的build议

  • 如果您期望创build组件的多个版本,请创build一个抽象类。 抽象类提供了一种简单而简单的方法来对组件进行版本化。 通过更新基类,所有inheritance类都会随着更改而自动更新。 另一方面,接口一旦创build就不能改变。 如果需要新版本的界面,则必须创build一个全新的界面。

  • 如果您正在创build的function将在各种不同的对象中有用,请使用界面。 抽象类应主要用于密切相关的对象,而接口则最适合为不相关类提供通用function。

  • 如果您正在devise小而简洁的function,请使用接口。 如果您正在devise大型function单元,请使用抽象类。

  • 如果要在组件的所有实现中提供通用的实现function,请使用抽象类。 抽象类允许您部分实现您的类,而接口不包含任何成员的实现。

如果您想提供完全replace您的实施的选项,请使用界面。 这尤其适用于主要组件之间的交互,因此应始终通过接口进行解耦。

偏好一个接口也可能有技术上的原因,例如在unit testing中启用模拟。

在组件内部,直接使用抽象类来访问类的层次结构可能没有问题。

如果您使用接口并具有实现类的层次结构,那么最好有一个包含实现的公共部分的抽象类。 例如

 interface Foo abstract class FooBase implements Foo class FunnyFoo extends FooBase class SeriousFoo extends FooBase 

你也可以有更多的抽象类inheritance对方的更复杂的层次结构。

我总是使用这些指导方针:

  • 使用多个TYPEinheritance的接口(因为.NET / Java不使用多inheritance)
  • 使用抽象类来实现types的可重用实现

主要关注的规则决定了一个阶级总是有一个主要的关注点,有0个或更多的人参加(见http://citeseer.ist.psu.edu/tarr99degrees.html )。 然后通过接口实现那些0个或更多的其他接口,然后类实现它必须实现的所有types(它自己的接口以及它实现的所有接口)。

在多重实现inheritance的世界中(例如C ++ / Eiffel),可以从实现接口的类inheritance。 (在理论上,实际上可能效果不好)

还有一些叫干的原则 – 不要重复自己。

在你的数据源的例子中,你会说在不同的实现之间有一些通用的代码。 对我来说,处理这个问题的最好办法似乎是在其中包含通用代码的抽象类,以及一些扩展它的具体类。

优点是通用代码中的每个错误修复都有利于所有具体的实现。

如果你只有界面,你将不得不维护几个相同的代码,这是麻烦的副本。

关于抽象+界面如果没有直接的理由,我不会这样做。 从抽象类抽取接口是一个容易的重构,所以我只会在实际需要的时候才会这样做。

一般准则请参考下面的SE问题:

界面与抽象类(通用OO)

界面的实际使用案例:

  1. Strategy_pattern的实现:将您的策略​​定义为一个接口。 运行时使用策略的具体实现之一dynamic切换实现。

  2. 在多个不相关的类中定义一个能力

抽象类的实际用例:

  1. Template_method_pattern的实现:定义一个algorithm的骨架。 儿童class不能改变algortihm结构,但他们可以重新定义儿童class的一部分实施。

  2. 当你想在多个相关的类之间共享“ ”关系的非静态和非最终variables。

使用abstradt类和接口:

如果你想要一个抽象类,你可以将抽象方法移动到接口,抽象类可以简单地实现该接口。 抽象类的所有用例都可以归入这个类。