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

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

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


当前回答

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

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

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

其他回答

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

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

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

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

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

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

在Submain.NET编码指南中很好地解释了接口上基类的情况:

基类与接口接口类型是部分值的描述,可能受许多对象类型支持。使用基类而不是接口只要可能。从版本控制透视,类更灵活而不是接口。有了课,你可以发布版本1.0,然后发布版本2.0向类中添加新方法。只要该方法不是抽象的,任何现有派生类都将继续功能不变。因为接口不支持实现继承应用于类的模式不适用于接口。添加方法与接口等效向基中添加抽象方法班实现接口将中断,因为类不实现新方法。接口适用于以下情况:几个不相关的类希望支持该协议。这些类已经建立了基类(对于实例一些是用户界面(UI)控件,一些是XMLWeb服务)。聚合不合适或不可行。在所有其他情况,类继承是更好的模型。

接口和基类表示两种不同形式的关系。

继承(基类)表示“is-a”关系。例如,狗或猫是宠物。这种关系始终代表着班级的(单一)目的(结合“单一责任原则”)。

另一方面,接口表示类的其他特性。我将其称为“是”关系,如“Foo是一次性的”,因此C#中的IDisposable接口。

现代风格是定义IPet和PetBase。

该接口的优点是其他代码可以使用它,而与其他可执行代码没有任何联系。完全“干净”。界面也可以混合。

但是基类对于简单的实现和通用实用程序很有用。因此,还提供一个抽象基类,以节省时间和代码。

从概念上讲,接口用于正式和半正式地定义对象将提供的一组方法。形式上表示一组方法名称和签名,半形式上表示与这些方法相关的人类可读文档。

接口只是API的描述(毕竟,API代表应用程序编程接口),它们不能包含任何实现,也不可能使用或运行接口。它们只明确约定你应该如何与对象交互。

类提供了一个实现,它们可以声明它们实现了零个、一个或多个接口。如果要继承类,则惯例是在类名前面加上“Base”。

基类和抽象基类(ABC)之间有区别。ABC将接口和实现混合在一起。计算机编程之外的抽象意味着“摘要”,即“抽象==接口”。然后抽象基类可以描述接口,也可以描述要继承的空的、部分的或完整的实现。

关于何时使用接口、抽象基类还是仅使用类的意见将根据您正在开发的内容以及您正在使用的语言而大不相同。接口通常仅与静态类型语言(如Java或C#)相关,但动态类型语言也可以有接口和抽象基类。例如,在Python中,Class和对象之间的区别很明显,前者声明它实现了一个接口,后者是一个类的实例,据说提供了该接口。在动态语言中,两个都是同一类实例的对象可以声明它们提供完全不同的接口。在Python中,这仅适用于对象属性,而方法在类的所有对象之间共享状态。然而,在Ruby中,对象可以具有每实例方法,因此同一类的两个对象之间的接口可能会根据程序员的需要而变化(然而,Ruby没有任何明确的接口声明方式)。

在动态语言中,对象的接口通常是隐式假设的,可以通过内省对象并询问它提供了什么方法(在跳转之前查看),也可以通过简单地尝试在对象上使用所需的接口并在对象不提供该接口时捕捉异常(请求原谅比请求许可更容易)。这可能导致“误报”,即两个接口具有相同的方法名称,但语义不同。然而,权衡是代码更加灵活,因为您不需要预先过度指定以预测代码的所有可能用途。