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

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

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


当前回答

让我们以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++),但能够实现多个接口,因此它允许您严格按照需要构造对象。

其他回答

基类的继承者应该具有“is a”关系。接口表示“实现”关系。因此,只有在继承者将维护关系时才使用基类。

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

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

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

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

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

当我第一次开始学习面向对象编程时,我犯了一个简单的、可能也是常见的错误,即使用继承来共享公共行为——即使这种行为对对象的本质来说并不重要。

为了进一步建立这个问题中经常使用的例子,有很多东西都是可拍的——女朋友、汽车、毛毯所以我可能有一个Petable类提供了这种常见的行为,以及从中继承的各种类。

然而,可拍不是这些物体的本质的一部分。还有很多更重要的概念对他们的天性至关重要——女朋友是人,汽车是陆地车辆,猫是哺乳动物。。。

行为应该首先被分配给接口(包括类的默认接口),并且只有当它们(a)对于作为更大类的子集的一大群类来说是公共的时,才应该被提升为基类——在相同的意义上,“猫”和“人”是“哺乳动物”的子集。

问题是,当你比我一开始更了解面向对象设计之后,你通常会自动完成这项工作,甚至不用考虑它。因此,“代码到接口,而不是抽象类”这句话的真实性变得如此明显,你很难相信任何人都会不厌其烦地说出它,并开始尝试将其他含义解读到其中。

我想补充的另一点是,如果一个类是纯抽象的-没有非抽象的、非继承的成员或方法暴露给子级、父级或客户端-那么为什么它是一个类?它可以被替换,在某些情况下被接口替换,在其他情况下被Null替换。

Josh Bloch在《有效的Java 2d》中说道:

首选接口而非抽象类

一些要点:

可以很容易地对现有类进行修改,以实现新的界面你所要做的就是添加所需的方法(如果还没有)存在并将implements子句添加到类声明。接口是定义混合的理想选择。粗略地说mixin是类可以使用的类型除了“主要”键入“”以声明它提供一些可选行为。例如Comparable是一个mixin接口允许类声明其实例的排序依据其他相互可比较的对象。接口允许构造非分层类型框架。类型层次结构为很适合组织一些事情,但是其他的事情并不是很简单刚性层次。接口通过每类包装的习惯用法。如果您使用抽象类来定义类型留下想要添加的程序员功能,别无选择使用继承。此外,你可以结合这些优点接口和抽象类提供抽象骨架实现类导出的非平凡接口。

另一方面,接口很难发展。如果您向接口添加一个方法,它将破坏它的所有实现。

PS:买这本书。它要详细得多。

在这篇Java World文章中对其进行了很好的解释。

就我个人而言,我倾向于使用接口来定义接口,即系统设计中指定如何访问某些内容的部分。

我会有一个类实现一个或多个接口,这并不罕见。

抽象类是我用来做其他事情的基础。

以下是上述文章JavaWorld.com文章的摘录,作者Tony Sintes,2001年4月20日


接口与抽象类选择接口和抽象类不是一个非此即彼的命题。如果你需要改变你的设计,让它成为一个界面。然而,您可能有提供一些默认行为的抽象类。抽象类是应用程序框架中的优秀候选者。抽象类允许您定义一些行为;它们迫使您的子类提供其他子类。例如,如果您有一个应用程序框架,抽象类可以提供默认服务,如事件和消息处理。这些服务允许应用程序插入应用程序框架。但是,有些特定于应用程序的功能只有您的应用程序才能执行。此类功能可能包括启动和关闭任务,这些任务通常依赖于应用程序。因此,抽象基类可以声明抽象的关闭和启动方法,而不是试图定义该行为本身。基类知道它需要这些方法,但抽象类让您的类承认它不知道如何执行这些操作;它只知道它必须发起行动。当需要启动时,抽象类可以调用启动方法。当基类调用此方法时,Java调用子类定义的方法。许多开发人员忘记了定义抽象方法的类也可以调用该方法。抽象类是创建计划继承层次结构的极好方法。对于类层次结构中的非叶类,它们也是一个很好的选择。类与接口

有人说你应该用接口来定义所有的类,但我认为这个建议有点极端。当我看到设计中的某些内容会频繁更改时,我会使用接口。

例如,Strategy模式允许您在程序中交换新的算法和过程,而不改变使用它们的对象。媒体播放器可能知道如何播放CD、MP3和wav文件。当然,您不希望将这些回放算法硬编码到播放器中;这将使添加像AVI这样的新格式变得困难。此外,您的代码将充斥着无用的case语句。为了增加伤害,您需要在每次添加新算法时更新这些案例陈述。总而言之,这不是一种非常面向对象的编程方式。使用Strategy模式,您可以简单地将算法封装在对象后面。如果这样做,您可以随时提供新的媒体插件。让我们将插件类称为MediaStrategy。该对象将有一个方法:playStream(流)。所以为了添加一个新的算法,我们只需扩展我们的算法类。现在,当程序遇到新的媒体类型时,它只是将流的播放委托给我们的媒体策略。当然,您需要一些管道来正确实例化所需的算法策略。这是一个使用界面的好地方。我们使用了Strategy模式,它清楚地表明设计中的一个位置将发生变化。因此,您应该将策略定义为一个接口。当您希望某个对象具有某种类型时,通常应该支持接口而不是继承;在本例中为MediaStrategy。依赖类型标识的继承是危险的;它将您锁定到特定的继承层次结构中。Java不允许多重继承,因此您不能扩展能够为您提供有用实现或更多类型标识的内容。