何时使用接口或抽象类? 何时使用两者?
虽然某些指导方针声明,当您想要为继承不明确的类定义合约( IDomesticated
)并且在类是另一个类的扩展时( Cat : Mammal
, Snake : Reptile
)的继承时,应该使用接口。 (在我看来)这些指导方针进入灰色地带的案例。
例如,说我的实现是Cat : Pet
。 Pet
是一个抽象类。 这应该扩展到Cat : Mammal, IDomesticated
Mammal
是抽象类和IDomesticated
是否是一个接口? 还是我与KISS / YAGNI原则相冲突(即使我不确定未来是否会有Wolf
阶级,这将无法继承Pet
)?
远离隐喻的Cat
和Pet
,让我们说我有一些类表示传入数据的来源。 他们都需要以某种方式实施相同的基地。 我可以在抽象的Source
类中实现一些通用代码并从中继承。 我也可以创建一个ISource
接口(对我来说感觉更“正确”),并在每个类中重新实现泛型代码(这不太直观)。 最后,我可以通过制作抽象类和界面来“吃蛋糕,吃东西”。 什么是最好的?
这两种情况提供了仅使用抽象类,仅使用接口并使用抽象类和接口的要点。 这些都是有效的选择,还是存在什么时候应该使用另一个的“规则”?
我想澄清一下,“同时使用抽象类和接口”包含了它们本质上表示同一事物( Source
和ISource
都具有相同成员)的情况,但该类在接口指定时添加了一般功能合同。
另外值得注意的是,这个问题主要针对不支持多继承的语言(如.NET和Java)。
作为第一条经验法则,我更喜欢基于.NET设计指南的抽象类超过接口。 推理应用比.NET更广泛,但在“框架设计指南”中有更好的解释。
抽象基类偏好背后的主要原因是版本控制,因为您始终可以在不破坏现有客户端的情况下向抽象基类添加新的虚拟成员。 这是不可能的接口。
有些情况下界面仍然是正确的选择(特别是当你不关心版本控制时),但是意识到优点和缺点可以使你做出正确的决定。
因此,在我继续之前作为部分答案:如果您决定首先对接口进行编码,那么只有接口和基类才有意义。 如果你允许一个接口,你必须只针对该接口进行编码,否则你会违反Liskov替换原则。 换句话说,即使您提供了实现该接口的基类,也不能让您的代码使用该基类。
如果你决定对一个基类进行编码,那么拥有一个接口是没有意义的。
如果您决定针对接口进行编码,则提供默认功能的基类是可选的。 这不是必要的,但可能会加速实施者的工作,所以你可以提供一个作为礼节。
想到的一个例子就是ASP.NET MVC。 请求管道在IController上运行,但是您通常使用一个Controller基类来实现行为。
最终答案:如果使用抽象基类,只使用它。 如果使用接口,基类对实现者是可选的礼貌。
更新:我不再喜欢抽象类超过接口,而且我也没有很长时间; 相反,我赞成使用SOLID作为指导,而不是继承。
(虽然我可以直接编辑上述文本,但它会彻底改变帖子的性质,并且由于少数人已经发现它有足够的价值来投票,所以我宁愿让原文站起来,而是添加此请注意,这篇文章的后半部分仍然有意义,因此删除它也将是一种耻辱。)
我倾向于使用基类(抽象与否)来描述什么是事物,而我使用接口来描述对象的能力。
猫是一种哺乳动物,但它的一个功能就是Pettable。
或者,换一种说法,类是名词,而接口映射更接近形容词。
从MSDN,抽象类与接口的建议
如果您期望创建组件的多个版本,请创建一个抽象类。 抽象类为您的组件版本提供了一种简单而简单的方法。 通过更新基类,所有继承类都会随着更改而自动更新。 另一方面,接口一旦创建就无法更改。 如果需要新版本的界面,则必须创建一个全新的界面。
如果您正在创建的功能在各种不同的对象中都有用,请使用界面。 抽象类应主要用于密切相关的对象,而接口最适合为无关类提供通用功能。
如果您正在设计小而精简的功能,请使用接口。 如果您正在设计大型功能单元,请使用抽象类。
如果您想在组件的所有实现中提供通用的实现功能,请使用抽象类。 抽象类允许您部分实现您的类,而接口不包含任何成员的实现。
上一篇: When to use interfaces or abstract classes? When to use both?
下一篇: Do we really need @staticmethod decorator in python to declare static method