我应该何时使用接口,何时使用基类?

如果我不想实际定义方法的基本实现,它应该始终是一个接口吗?

如果我有狗和猫的课。为什么我要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)提供接口,因为它们可以逐个宠物放置,但我不知道该为普通宠物使用哪个接口。


当前回答

接口

大多数语言允许您实现多个接口修改接口是一项突破性的更改。所有实现都需要重新编译/修改。所有成员都是公共的。实现必须实现所有成员。接口有助于解耦。您可以使用模拟框架模拟接口后面的任何内容接口通常表示一种行为接口实现彼此分离/隔离

基类

允许您添加一些通过派生免费获得的默认实现(从C#8.0通过接口可以获得默认实现)除了C++,您只能从一个类派生。即使可以从多个类中选择,这通常也是一个坏主意。更改基类相对容易。派生不需要做任何特殊的事情基类可以声明可由派生访问的受保护函数和公共函数抽象基类不能像接口那样容易被模仿基类通常表示类型层次结构(IS A)类派生可能会依赖于一些基本行为(具有复杂的父实现知识)。如果你对一个人的基本实现进行更改并打破其他人,事情可能会很混乱。

其他回答

接口应较小。真的很小。如果你真的要分解你的对象,那么你的接口可能只包含一些非常具体的方法和财产。

抽象类是快捷方式。PetBase的所有衍生产品都有你可以编写一次代码并完成的东西吗?如果是的话,那么是抽象类的时候了。

抽象类也是有限的。虽然它们为您提供了生成子对象的绝佳捷径,但任何给定对象只能实现一个抽象类。很多时候,我发现这是抽象类的局限性,这也是我使用大量接口的原因。

抽象类可以包含多个接口。您的PetBase抽象类可以实现IPet(宠物有主人)和IDigestion(宠物吃,或者至少它们应该吃)。然而,PetBase可能不会实施IMammal,因为并非所有宠物都是哺乳动物,也并非所有哺乳动物都是宠物。您可以添加一个扩展PetBase的MammalPetBase并添加IMammal。FishBase可以有PetBase并添加IFish。IFish将使用ISwim和IUnderwaterBreaker作为接口。

是的,对于简单的示例来说,我的示例非常复杂,但这是接口和抽象类如何协同工作的一部分。

感谢Jon Limjap的回答,但我想为接口和抽象基类的概念添加一些解释

接口类型与抽象基类

改编自Pro C#5.0和.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#不支持类的多重继承。因此,如果您想创建一辆小型货车,它是一辆汽车,并且是一种可克隆类型,那么您无法做到:

// Nope! Multiple inheritance is not possible in C#
// for classes.
public class MiniVan : Car, CloneableType
{
}

正如您所猜测的,接口类型起到了拯救作用。定义接口后,可以可以由任何类或结构、任何层次结构、任何命名空间或任何程序集中实现(用任何.NET编程语言编写)。正如您所看到的,接口是高度多态的。考虑System命名空间中定义的名为ICloneable的标准.NET接口。这接口定义了一个名为Clone()的方法:

public interface ICloneable
{
object Clone();
}

继承还有其他优点,例如变量能够保存父类或继承类的对象(无需将其声明为泛型,如“object”)。

例如,在.NET WinForms中,大多数UI组件都是从System.Windows.Forms.Control派生的,因此声明为的变量可以“容纳”几乎所有UI元素,无论是按钮、ListView还是其他元素。当然,现在你不能访问项目的所有财产或方法,但你可以拥有所有基本的东西——这可能很有用。

列出你的对象必须要做的事情,你的对象可以做的事情。必须指明你的基类型,可以指明你的接口。

例如,您的PetBase必须呼吸,而您的IPet可能会DoTricks。

对问题域的分析将帮助您定义精确的层次结构。

这取决于您的要求。如果IPet足够简单,我更愿意实现它。否则,如果PetBase实现了大量您不想复制的功能,那么就试试看。

实现基类的缺点是需要重写(或新的)现有方法。这使它们成为虚拟方法,这意味着您必须小心如何使用对象实例。

最后,.NET的单一继承让我很头疼。一个天真的例子:假设你正在创建一个用户控件,那么你就继承了UserControl。但是,现在你被禁止继承PetBase。这迫使您重新组织,例如创建PetBase类成员。