我应该何时使用接口,何时使用基类?
如果我不想实际定义方法的基本实现,它应该始终是一个接口吗?
如果我有狗和猫的课。为什么我要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)提供接口,因为它们可以逐个宠物放置,但我不知道该为普通宠物使用哪个接口。
我应该何时使用接口,何时使用基类?
如果我不想实际定义方法的基本实现,它应该始终是一个接口吗?
如果我有狗和猫的课。为什么我要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)提供接口,因为它们可以逐个宠物放置,但我不知道该为普通宠物使用哪个接口。
当前回答
从概念上讲,接口用于正式和半正式地定义对象将提供的一组方法。形式上表示一组方法名称和签名,半形式上表示与这些方法相关的人类可读文档。
接口只是API的描述(毕竟,API代表应用程序编程接口),它们不能包含任何实现,也不可能使用或运行接口。它们只明确约定你应该如何与对象交互。
类提供了一个实现,它们可以声明它们实现了零个、一个或多个接口。如果要继承类,则惯例是在类名前面加上“Base”。
基类和抽象基类(ABC)之间有区别。ABC将接口和实现混合在一起。计算机编程之外的抽象意味着“摘要”,即“抽象==接口”。然后抽象基类可以描述接口,也可以描述要继承的空的、部分的或完整的实现。
关于何时使用接口、抽象基类还是仅使用类的意见将根据您正在开发的内容以及您正在使用的语言而大不相同。接口通常仅与静态类型语言(如Java或C#)相关,但动态类型语言也可以有接口和抽象基类。例如,在Python中,Class和对象之间的区别很明显,前者声明它实现了一个接口,后者是一个类的实例,据说提供了该接口。在动态语言中,两个都是同一类实例的对象可以声明它们提供完全不同的接口。在Python中,这仅适用于对象属性,而方法在类的所有对象之间共享状态。然而,在Ruby中,对象可以具有每实例方法,因此同一类的两个对象之间的接口可能会根据程序员的需要而变化(然而,Ruby没有任何明确的接口声明方式)。
在动态语言中,对象的接口通常是隐式假设的,可以通过内省对象并询问它提供了什么方法(在跳转之前查看),也可以通过简单地尝试在对象上使用所需的接口并在对象不提供该接口时捕捉异常(请求原谅比请求许可更容易)。这可能导致“误报”,即两个接口具有相同的方法名称,但语义不同。然而,权衡是代码更加灵活,因为您不需要预先过度指定以预测代码的所有可能用途。
其他回答
胡安,
我喜欢将接口视为一种描述类的方式。一个特定的犬类,比如约克郡梗,可能是母犬类的后代,但它也执行IFurry、IStubby和IYippieDog。所以类定义了类是什么,但接口告诉了我们关于它的事情。
这样做的好处是,例如,它允许我收集所有的IYippieDog,并将它们放入我的海洋收藏。因此,现在我可以跨越一组特定的对象,找到符合我所关注的标准的对象,而不必过于仔细地检查类。
我发现接口确实应该定义类的公共行为的子集。如果它为实现的所有类定义了所有公共行为,那么它通常不需要存在。他们没有告诉我任何有用的东西。
但这种想法与每个类都应该有一个接口的想法背道而驰,您应该编写接口代码。这很好,但最终会有很多一对一的类接口,这会让事情变得混乱。我知道这样做并不需要花费任何费用,现在你可以轻松地交换东西。然而,我发现我很少这样做。大多数时候,我只是在原地修改现有的类,如果该类的公共接口需要更改,我会遇到完全相同的问题,但我现在必须在两个地方更改它。
所以,如果你像我一样思考,你肯定会说《猫和狗》是IPettable的。这是一个符合两者的特征。
但另一个问题是,它们是否应该有相同的基类?问题是,它们是否需要被广泛地视为同一事物。当然,它们都是动物,但这是否符合我们将如何一起使用它们。
假设我想收集所有动物类并将它们放在我的方舟容器中。
或者它们需要是哺乳动物吗?也许我们需要某种跨动物挤奶工厂?
他们甚至需要联系在一起吗?仅仅知道它们都是IPettable就足够了吗?
当我真的只需要一个类时,我常常会感到想要派生出一个完整的类层次结构。我做这件事是为了期待有一天我可能会需要它,但通常我从来不会这样做。即使我做了,我通常也会发现我必须做很多事情来修复它。这是因为我创建的第一个类不是狗,我没有那么幸运,而是鸭嘴兽。现在,我的整个类层次结构都基于这个奇怪的案例,我有很多浪费的代码。
你可能还会发现,并不是所有的猫都是IPettable的(比如那个无毛的猫)。现在您可以将该接口移动到所有适合的派生类。你会发现,突然之间,猫不再是PettableBase的产物,这是一个小得多的变化。
Josh Bloch在《有效的Java 2d》中说道:
首选接口而非抽象类
一些要点:
可以很容易地对现有类进行修改,以实现新的界面你所要做的就是添加所需的方法(如果还没有)存在并将implements子句添加到类声明。接口是定义混合的理想选择。粗略地说mixin是类可以使用的类型除了“主要”键入“”以声明它提供一些可选行为。例如Comparable是一个mixin接口允许类声明其实例的排序依据其他相互可比较的对象。接口允许构造非分层类型框架。类型层次结构为很适合组织一些事情,但是其他的事情并不是很简单刚性层次。接口通过每类包装的习惯用法。如果您使用抽象类来定义类型留下想要添加的程序员功能,别无选择使用继承。此外,你可以结合这些优点接口和抽象类提供抽象骨架实现类导出的非平凡接口。
另一方面,接口很难发展。如果您向接口添加一个方法,它将破坏它的所有实现。
PS:买这本书。它要详细得多。
下面是接口和基类的基本和简单定义:
基类=对象继承。接口=功能继承。
干杯
关于C#,在某些意义上,接口和抽象类可以互换。然而,区别在于:i)接口不能实现代码;ii)由于这一点,接口不能进一步调用堆栈上的子类;以及iii)抽象类只能在类上继承,而多个接口可以在类上实现。
我建议尽可能使用组合而不是继承。使用接口,但对基本实现使用成员对象。这样,您可以定义一个工厂,该工厂构造对象以某种方式运行。如果要更改行为,则创建一个新的工厂方法(或抽象工厂),以创建不同类型的子对象。
在某些情况下,如果所有可变行为都在帮助对象中定义,那么您可能会发现主对象根本不需要接口。
因此,您可能会得到一个带有IFurBehavior参数的宠物,而不是IPet或PetBase。IFurBehavior参数由PetFactory的CreateDog()方法设置。shed()方法调用的就是这个参数。
如果你这样做,你会发现你的代码更加灵活,你的大多数简单对象都处理非常基本的系统范围行为。
即使在多种继承语言中,我也推荐这种模式。