我应该何时使用接口,何时使用基类?
如果我不想实际定义方法的基本实现,它应该始终是一个接口吗?
如果我有狗和猫的课。为什么我要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)提供接口,因为它们可以逐个宠物放置,但我不知道该为普通宠物使用哪个接口。
我应该何时使用接口,何时使用基类?
如果我不想实际定义方法的基本实现,它应该始终是一个接口吗?
如果我有狗和猫的课。为什么我要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)提供接口,因为它们可以逐个宠物放置,但我不知道该为普通宠物使用哪个接口。
当前回答
关于C#,在某些意义上,接口和抽象类可以互换。然而,区别在于:i)接口不能实现代码;ii)由于这一点,接口不能进一步调用堆栈上的子类;以及iii)抽象类只能在类上继承,而多个接口可以在类上实现。
其他回答
当我第一次开始学习面向对象编程时,我犯了一个简单的、可能也是常见的错误,即使用继承来共享公共行为——即使这种行为对对象的本质来说并不重要。
为了进一步建立这个问题中经常使用的例子,有很多东西都是可拍的——女朋友、汽车、毛毯所以我可能有一个Petable类提供了这种常见的行为,以及从中继承的各种类。
然而,可拍不是这些物体的本质的一部分。还有很多更重要的概念对他们的天性至关重要——女朋友是人,汽车是陆地车辆,猫是哺乳动物。。。
行为应该首先被分配给接口(包括类的默认接口),并且只有当它们(a)对于作为更大类的子集的一大群类来说是公共的时,才应该被提升为基类——在相同的意义上,“猫”和“人”是“哺乳动物”的子集。
问题是,当你比我一开始更了解面向对象设计之后,你通常会自动完成这项工作,甚至不用考虑它。因此,“代码到接口,而不是抽象类”这句话的真实性变得如此明显,你很难相信任何人都会不厌其烦地说出它,并开始尝试将其他含义解读到其中。
我想补充的另一点是,如果一个类是纯抽象的-没有非抽象的、非继承的成员或方法暴露给子级、父级或客户端-那么为什么它是一个类?它可以被替换,在某些情况下被接口替换,在其他情况下被Null替换。
接口应较小。真的很小。如果你真的要分解你的对象,那么你的接口可能只包含一些非常具体的方法和财产。
抽象类是快捷方式。PetBase的所有衍生产品都有你可以编写一次代码并完成的东西吗?如果是的话,那么是抽象类的时候了。
抽象类也是有限的。虽然它们为您提供了生成子对象的绝佳捷径,但任何给定对象只能实现一个抽象类。很多时候,我发现这是抽象类的局限性,这也是我使用大量接口的原因。
抽象类可以包含多个接口。您的PetBase抽象类可以实现IPet(宠物有主人)和IDigestion(宠物吃,或者至少它们应该吃)。然而,PetBase可能不会实施IMammal,因为并非所有宠物都是哺乳动物,也并非所有哺乳动物都是宠物。您可以添加一个扩展PetBase的MammalPetBase并添加IMammal。FishBase可以有PetBase并添加IFish。IFish将使用ISwim和IUnderwaterBreaker作为接口。
是的,对于简单的示例来说,我的示例非常复杂,但这是接口和抽象类如何协同工作的一部分。
一个重要的区别是,只能继承一个基类,但可以实现多个接口。因此,如果您绝对确定不需要同时继承不同的基类,那么只需要使用基类。此外,如果你发现你的接口越来越大,那么你应该开始把它分成几个逻辑块来定义独立的功能,因为没有规则规定你的类不能实现所有的功能(或者你可以定义一个不同的接口来继承所有的功能)。
在Submain.NET编码指南中很好地解释了接口上基类的情况:
基类与接口接口类型是部分值的描述,可能受许多对象类型支持。使用基类而不是接口只要可能。从版本控制透视,类更灵活而不是接口。有了课,你可以发布版本1.0,然后发布版本2.0向类中添加新方法。只要该方法不是抽象的,任何现有派生类都将继续功能不变。因为接口不支持实现继承应用于类的模式不适用于接口。添加方法与接口等效向基中添加抽象方法班实现接口将中断,因为类不实现新方法。接口适用于以下情况:几个不相关的类希望支持该协议。这些类已经建立了基类(对于实例一些是用户界面(UI)控件,一些是XMLWeb服务)。聚合不合适或不可行。在所有其他情况,类继承是更好的模型。
从概念上讲,接口用于正式和半正式地定义对象将提供的一组方法。形式上表示一组方法名称和签名,半形式上表示与这些方法相关的人类可读文档。
接口只是API的描述(毕竟,API代表应用程序编程接口),它们不能包含任何实现,也不可能使用或运行接口。它们只明确约定你应该如何与对象交互。
类提供了一个实现,它们可以声明它们实现了零个、一个或多个接口。如果要继承类,则惯例是在类名前面加上“Base”。
基类和抽象基类(ABC)之间有区别。ABC将接口和实现混合在一起。计算机编程之外的抽象意味着“摘要”,即“抽象==接口”。然后抽象基类可以描述接口,也可以描述要继承的空的、部分的或完整的实现。
关于何时使用接口、抽象基类还是仅使用类的意见将根据您正在开发的内容以及您正在使用的语言而大不相同。接口通常仅与静态类型语言(如Java或C#)相关,但动态类型语言也可以有接口和抽象基类。例如,在Python中,Class和对象之间的区别很明显,前者声明它实现了一个接口,后者是一个类的实例,据说提供了该接口。在动态语言中,两个都是同一类实例的对象可以声明它们提供完全不同的接口。在Python中,这仅适用于对象属性,而方法在类的所有对象之间共享状态。然而,在Ruby中,对象可以具有每实例方法,因此同一类的两个对象之间的接口可能会根据程序员的需要而变化(然而,Ruby没有任何明确的接口声明方式)。
在动态语言中,对象的接口通常是隐式假设的,可以通过内省对象并询问它提供了什么方法(在跳转之前查看),也可以通过简单地尝试在对象上使用所需的接口并在对象不提供该接口时捕捉异常(请求原谅比请求许可更容易)。这可能导致“误报”,即两个接口具有相同的方法名称,但语义不同。然而,权衡是代码更加灵活,因为您不需要预先过度指定以预测代码的所有可能用途。