我应该在什么时候使用接口,什么时候使用基类?
如果我不想定义方法的基本实现,是否总是应该使用接口?
如果我有一个Dog和Cat类,为什么我要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)等功能提供接口,因为这些功能可以针对每个宠物进行设置,但我不明白为什么要为通用Pet使用哪种方式。
我应该在什么时候使用接口,什么时候使用基类?
如果我不想定义方法的基本实现,是否总是应该使用接口?
如果我有一个Dog和Cat类,为什么我要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)等功能提供接口,因为这些功能可以针对每个宠物进行设置,但我不明白为什么要为通用Pet使用哪种方式。
列出你的对象必须拥有、具备或执行的事项清单,以及你的对象可以(或可能)拥有、具备或执行的事项清单。其中,必须表示你的基础类型,而可以表示你的接口。
例如,你的PetBase 必须呼吸,而你的IPet 可能会表演技巧。
通过对问题域的分析,有助于定义精确的层次结构。
感谢Jon Limjap的回答,但我想为接口和抽象基类的概念添加一些解释。
接口类型 vs. 抽象基类
摘自Pro C# 5.0 and the .NET 4.5 Framework书籍。
接口类型看起来非常类似于抽象基类。请记住,当一个类被标记为抽象时,它可以定义任意数量的抽象成员,以向所有派生类型提供多态接口。然而,即使一个类定义了一组抽象成员,它也可以自由地定义任意数量的构造函数、字段数据、带实现的非抽象成员等等。相反,接口仅包含抽象成员定义。抽象父类建立的多态接口存在一个主要限制,即仅有派生类型支持抽象父类定义的成员。但是,在更大的软件系统中,很常见开发多个类层次结构,除了System.Object之外没有共同的父级。鉴于抽象基类中的抽象成员仅适用于派生类型,我们无法配置不同层次结构中的类型以支持相同的多态接口。例如,假设您已定义了以下抽象类:public abstract class CloneableType
{
// Only derived types can support this
// "polymorphic interface." Classes in other
// hierarchies have no access to this abstract
// member.
public abstract object Clone();
}
根据这个定义,只有扩展CloneableType的成员才能支持Clone()方法。如果您创建了一组不扩展此基类的类,那么您无法获得此多态接口。此外,您可能还记得,C#不支持类的多重继承。因此,如果您想创建一个既是Car又是CloneableType的MiniVan,您将无法实现:
// Nope! Multiple inheritance is not possible in C#
// for classes.
public class MiniVan : Car, CloneableType
{
}
正如你所猜测的那样,接口类型能够解决问题。一旦定义了一个接口,任何类或结构都可以在任何层次结构、任何命名空间或任何程序集中(使用任何.NET编程语言)实现它。可以看出,接口是高度多态的。考虑一下标准的.NET接口ICloneable,它定义在System命名空间中。该接口定义了一个名为Clone()的方法:
public interface ICloneable
{
object Clone();
}
运用你自己的判断力并且要聪明。不要总是听从别人(比如我)所说的话。你会听到“优先使用接口而不是抽象类”,但这实际上取决于情况。它取决于这个类是什么。
在上述有对象层次结构的情况下,接口是一个好主意。接口有助于处理这些对象的集合,并且在实现与层次结构中任何对象一起工作的服务时也很有帮助。你只需定义与层次结构中的对象一起工作的契约即可。
另一方面,当你实现一堆共享通用功能的服务时,你可以将共同功能分离成一个完全独立的类,或者将其移动到一个公共基类中并使其抽象化,以便没有人可以实例化基类。
还要考虑如何随着时间的推移支持你的抽象化。接口是固定的:你发布一个接口作为任何类型都可以实现的一组功能的契约。基类可以随着时间的推移进行扩展。这些扩展成为每个派生类的一部分。
接口在类方面具有明显的优势,可以进行“热插拔”。将一个类从一个父类更改为另一个父类通常会导致很多工作量,但接口通常可以轻松移除和更改,而对实现类的影响不大。这在你有几种行为集可能需要一个类来实现的情况下尤其有用。
这在我的领域——游戏编程中特别适用。基础类可能会因为继承对象可能需要的大量行为而变得臃肿。使用接口可以轻松快速地将不同的行为添加或移除到对象中。例如,如果我为想要反射伤害的对象创建一个“IDamageEffects”接口,则我可以轻松应用于各种游戏对象,并随时更改主意。比如说,我设计了一种初始类来用于“静态”的装饰性物品,最初我决定它们是不可摧毁的。后来,我可能会决定如果他们能够爆炸会更加有趣,所以我可以改变类来实现“IDamageEffects”接口,这比更换基础类或创建新的对象层次结构要容易得多。
继承还有其他优点,例如变量能够容纳父类或继承类的对象(无需将其声明为通用类型,如“Object”)。
例如,在.NET WinForms中,大多数UI组件都派生自System.Windows.Forms.Control,因此声明为该类型的变量可以“容纳”几乎任何UI元素 - 按钮、ListView等等。当然,您无法访问项目的所有属性或方法,但您将拥有所有基本内容 - 这可能很有用。
如果其他开发人员没有使用自己的基类的理由,而且你预见到版本问题(参见http://haacked.com/archive/2008/02/21/versioning-issues-with-abstract-base-classes-and-interfaces.aspx),那么你应该使用一个基类。
如果继承的开发人员有任何理由使用自己的基类来实现你的类型接口,并且你不认为接口会改变,那么就使用接口。在这种情况下,你仍然可以添加一个默认的基类来实现接口,以方便使用。