何时使用接口或抽象类? 何时使用?
虽然某些指导方针声明,当您想要为inheritance不清楚的类定义合同( IDomesticated
),并且在类是另一个类的扩展时( Cat : Mammal
, Snake : Reptile
)的inheritance,您应该使用接口。 (在我看来)这些指导方针进入灰色地带的情况。
例如,说我的实施是Cat : Pet
。 Pet
是一个抽象的类。 是否应该扩展到Cat : Mammal, IDomesticated
Mammal
是抽象类和IDomesticated
是一个接口? 还是我和KISS / YAGNI的原则相冲突(即使我不确定将来会不会有一个Wolf
阶级,而这个阶级是不能从Pet
inheritance的)?
摆脱隐喻的Cat
和Pet
,让我们说我有一些代表传入数据来源的类。 他们都需要以某种方式实施相同的基地。 我可以在一个抽象的Source
类中实现一些通用的代码,并inheritance它。 我也可以创build一个ISource
接口(对我来说感觉更“正确”),并在每个类中重新实现generics代码(这不太直观)。 最后,我可以通过制作抽象类和接口来“吃蛋糕”。 什么是最好的?
这两种情况提出只使用抽象类,只使用一个接口,同时使用抽象类和接口。 这些都是有效的select,还是有什么“规则”,什么时候应该用于另一个?
我想澄清的是,通过“同时使用抽象类和接口”来包含它们本质上代表相同事物( Source
和ISource
都具有相同成员)的情况,但是该类在接口指定时添加了一般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)
界面的实际使用案例:
-
Strategy_pattern的实现:将您的策略定义为一个接口。 运行时使用策略的具体实现之一dynamic切换实现。
-
在多个不相关的类中定义一个能力 。
抽象类的实际用例:
-
Template_method_pattern的实现:定义一个algorithm的骨架。 儿童class不能改变algortihm结构,但他们可以重新定义儿童class的一部分实施。
-
当你想在多个相关的类之间共享“ 有 ”关系的非静态和非最终variables。
使用abstradt类和接口:
如果你想要一个抽象类,你可以将抽象方法移动到接口,抽象类可以简单地实现该接口。 抽象类的所有用例都可以归入这个类。