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

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

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


当前回答

首选接口而非抽象类

根本原因需要考虑的要点[这里已经提到了两个]是:

接口更灵活,因为一个类可以实现多个接口。由于Java没有多重继承,因此使用抽象类防止用户使用任何其他类等级制度通常,当没有默认值时,首选接口实现或状态。Java集合提供了这(地图、集合等)。抽象类的优点是允许更好的转发兼容性。一旦客户端使用了一个接口,就不能更改它;如果它们使用抽象类,您仍然可以添加行为而不使用破坏现有代码。如果兼容性是一个问题,请考虑使用抽象类。即使您有默认实现或内部状态,考虑提供一个接口及其抽象实现。这将有助于客户,但如果所需[1]。当然,这个问题已经讨论了很长时间其他地方[2,3]。

[1] 当然,它增加了更多的代码,但如果简洁是您的主要关注点,那么您可能应该首先避免使用Java!

[2] Joshua Bloch,《有效的Java》,第16-18项。

[3] http://www.codeproject.com/KB/ar...

其他回答

感谢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();
}

一个重要的区别是,只能继承一个基类,但可以实现多个接口。因此,如果您绝对确定不需要同时继承不同的基类,那么只需要使用基类。此外,如果你发现你的接口越来越大,那么你应该开始把它分成几个逻辑块来定义独立的功能,因为没有规则规定你的类不能实现所有的功能(或者你可以定义一个不同的接口来继承所有的功能)。

以前关于将抽象类用于公共实现的评论无疑是正确的。我还没有看到提到的一个好处是,使用接口可以更容易地实现用于单元测试的模拟对象。如Jason Cohen所描述的那样定义IPet和PetBase,使您能够轻松模拟不同的数据条件,而无需物理数据库的开销(直到您决定是时候测试真实数据为止)。

让我们以Dog和Cat类为例,使用C#进行演示:

狗和猫都是动物,特别是四足哺乳动物(动物太普通了)。让我们假设您有一个抽象类Mammal,用于这两个类:

public abstract class Mammal

此基类可能具有默认方法,例如:

喂养朋友

所有这些都是在两个物种之间或多或少具有相同实现的行为。要定义此项,您需要:

public class Dog : Mammal
public class Cat : Mammal

现在让我们假设还有其他哺乳动物,我们通常会在动物园里看到:

public class Giraffe : Mammal
public class Rhinoceros : Mammal
public class Hippopotamus : Mammal

这仍然有效,因为Feed()和Mate()功能的核心仍然相同。

然而,长颈鹿、犀牛和河马并不是你可以用来做宠物的动物。这就是界面将有用的地方:

public interface IPettable
{
    IList<Trick> Tricks{get; set;}
    void Bathe();
    void Train(Trick t);
}

上述合同的执行在猫和狗之间是不同的;将它们的实现放在抽象类中继承将是一个坏主意。

狗和猫的定义现在应该如下:

public class Dog : Mammal, IPettable
public class Cat : Mammal, IPettable

理论上,您可以从更高的基类重写它们,但本质上,接口允许您只将所需的内容添加到类中,而不需要继承。

因此,由于您通常只能从一个抽象类继承(在大多数静态类型的OO语言中,即……例外包括C++),但能够实现多个接口,因此它允许您严格按照需要构造对象。

通常,您应该喜欢接口而不是抽象类。使用抽象类的一个原因是,如果在具体类之间有共同的实现。当然,您仍然应该声明一个接口(IPet),并让一个抽象类(PetBase)实现该接口。使用小的、不同的接口,您可以使用多个来进一步提高灵活性。接口允许最大限度的灵活性和跨边界类型的可移植性。当跨越边界传递引用时,始终传递接口而不是具体类型。这允许接收端确定具体实施,并提供最大的灵活性。当以TDD/BDD方式编程时,这是绝对正确的。

“四人帮”在他们的书中说:“因为继承将子类暴露于其父类实现的细节,所以人们常说‘继承破坏了封装’。”。我相信这是真的。