接口与基类的区别

836

我应该在什么时候使用接口,什么时候使用基类?

如果我不想定义方法的基本实现,是否总是应该使用接口?

如果我有一个Dog和Cat类,为什么我要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)等功能提供接口,因为这些功能可以针对每个宠物进行设置,但我不明白为什么要为通用Pet使用哪种方式。


11
我认为你应该考虑的一个要点是——接口可能会带来一些限制,直到非常晚的阶段你才会意识到这些限制。例如,在 .NET 中,您无法序列化接口成员变量,因此,如果您有一个名为 Zoo 的类和一个 IAnimals 数组成员变量,您将无法序列化 Zoo(这意味着编写 WebServices 或其他需要序列化的东西将会很麻烦)。 - synhershko
2
这个问题可能有助于理解接口的概念。https://dev59.com/cWoy5IYBdhLWcg3wdt4L - gprathour
我只是好奇。我在 CLR via C# 中遇到了以下摘录:“我倾向于使用接口技术而不是基类型技术,因为基类型技术不允许开发人员选择最适合特定情况的基类型。” 我无法理解摘录中的意思。我们可以创建几个基类型,并为其中任何一个创建派生类型,因此开发人员可以选择基类型。请问有人能解释一下吗?我相信这可能是这个问题的一部分。还是我应该发布另一个关于具体摘录的问题? - qqqqqqq
38个回答

3

之前有关使用抽象类进行常见实现的评论绝对是正确的。但还有一个好处未被提及,那就是使用接口使得实现单元测试时更容易实现模拟对象。按照Jason Cohen所描述的定义IPet和PetBase可以轻松地模拟不同的数据条件,而无需使用实际数据库(直到你决定测试真正的东西为止)。


3

除非你知道基类的含义并且在这种情况下它适用,否则不要使用基类。如果适用,请使用它,否则请使用接口。但请注意有关小接口的答案。

公共继承在对象导向设计中被过度使用,并且表达了比大多数开发人员意识到或愿意承担的更多内容。请参阅里氏替换原则

简而言之,如果 A “是一个” B,那么 A 不需要比 B 更多,也不能比 B 更少,对于它公开的每个方法都是如此。


3

从概念上讲,接口用于正式和半正式地定义对象提供的一组方法。正式意味着一组方法名称和签名,而半正式则指与这些方法相关联的人类可读文档。

接口仅是API的描述(毕竟API代表应用程序编程接口),它们不能包含任何实现,并且无法使用或运行接口。它们只明确了您应如何与对象交互的合同。

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

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

使用接口(interfaces)、抽象基类(abstract base classes)或仅使用类(classes)的选择,取决于您正在开发的内容以及您正在使用的编程语言。接口通常只与静态类型语言(如Java或C#)相关联,但动态类型语言也可以有接口和抽象基类。例如,在Python中,Class声明它实现了一个interface,而对象是一个class的实例,并被认为提供该interface。在动态语言中,两个都是同一class实例的对象可能声明它们提供完全不同的interfaces。在Ruby中,对象可以具有每个实例方法,因此两个相同class的对象之间的interface可以根据程序员的需要变化(但是,Ruby没有任何显式声明接口的方式)。
在动态语言中,对于对象的接口通常是隐含的,要么通过内省对象并询问它提供哪些方法(“先看再跳”),要么最好是尝试在对象上使用所需的接口,并在对象不提供该接口时捕获异常(“宁愿请求原谅,而不是事先征得许可”)。这可能会导致“误报”,即两个接口具有相同的方法名称,但在语义上却不同。然而,权衡考虑的是,您的代码更加灵活,因为您不需要过度指定以预测代码的所有可能用途。

2

我发现在以下用例中,Interface > Abstract > Concrete 的模式是有效的:

1.  You have a general interface (eg IPet)
2.  You have a implementation that is less general (eg Mammal)
3.  You have many concrete members (eg Cat, Dog, Ape)

抽象类定义了具体类的默认共享属性,同时强制执行接口。例如:
public interface IPet{

    public boolean hasHair();

    public boolean walksUprights();

    public boolean hasNipples();
}

现在,由于所有哺乳动物都有毛和乳头(据我所知,我不是动物学家),因此我们可以将其整合到抽象基类中。

public abstract class Mammal() implements IPet{

     @override
     public walksUpright(){
         throw new NotSupportedException("Walks Upright not implemented");
     }

     @override
     public hasNipples(){return true}

     @override
     public hasHair(){return true}

具体来说,具体的类只需定义它们直立行走。

public class Ape extends Mammal(){

    @override
    public walksUpright(return true)
}

public class Catextends Mammal(){

    @override
    public walksUpright(return false)
}

当有很多具体类时,这种设计非常好,而且您不想维护样板文件以编程接口。如果向接口添加新方法,则会破坏所有生成的类,因此仍然可以获得接口方法的优势。

在这种情况下,抽象类也可以是具体类;但是,抽象标识有助于强调正在使用此模式。


2

接口提供了与其他代码通信的层。类的所有公共属性和方法默认情况下都实现了隐式接口。我们还可以将接口定义为角色,每当任何类需要扮演该角色时,它都必须实现它,并根据实现它的类给出不同形式的实现。因此,当您谈论接口时,您正在谈论多态性,而当您谈论基类时,您正在谈论继承。这是面向对象编程的两个概念!!!


2

关于 C#,在某些方面,接口和抽象类是可以互换的。然而,它们之间的区别在于:i)接口不能实现代码;ii)因此,接口不能调用更高层次的子类;iii)只有抽象类可以被继承到一个类中,而一个类可以实现多个接口。


2

这取决于您的要求。如果IPet足够简单,我更喜欢实现它。否则,如果PetBase实现了大量您不想复制的功能,则可以使用它。

实现基类的缺点是需要override(或new)现有方法。这使它们成为虚拟方法,这意味着您必须小心使用对象实例。

最后,.NET的单一继承让我感到困扰。一个天真的例子:假设您正在制作一个用户控件,因此您继承了UserControl。但是,现在您无法继承PetBase。这迫使您重新组织,例如将PetBase类成员化。


2

在我需要其中之一时才会实现,我更喜欢接口而不是抽象类,因为这样可以提供更多的灵活性。如果某些继承类中存在共同的行为,我会将其上移并创建一个抽象基类。我认为不需要同时使用两者,因为它们本质上服务于相同的目的,并且同时使用两者是糟糕的代码味道(在我看来),这意味着解决方案被过度设计。


1

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

如果:

  1. 您有纯粹的抽象方法,并且没有非抽象方法
  2. 您没有默认实现非抽象方法(除了Java 8语言,其中接口方法提供默认实现)
  3. 如果您正在使用Java 8,则现在接口将为某些非抽象方法提供默认实现。与抽象类相比,这将使接口更易于使用。

请查看此SE 问题以获取更多详细信息。

如果我不想实际定义方法的基本实现,它是否总是一个接口?

是的。这样做更好,更清晰。即使您拥有带有一些抽象方法的基类,也让基类通过接口扩展抽象方法。您可以在不更改基类的情况下更改接口。

Java示例:

abstract class PetBase implements IPet {
// Add all abstract methods in IPet interface and keep base class clean. 
   Base class will contain only non abstract methods and static methods.
}

如果我有一个Dog和Cat类。为什么要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)拥有接口,因为这些可以针对每个宠物进行设置,但我不知道应该在通用Pet中使用哪个。
我更喜欢基类实现接口。
 abstract class PetBase implements IPet {
 // Add all abstract methods in IPet
 }

 /*If ISheds,IBarks is common for Pets, your PetBase can implement ISheds,IBarks. 
  Respective implementations of PetBase can change the behaviour in their concrete classes*/

 abstract class PetBase implements IPet,ISheds,IBarks {
 // Add all abstract methods in respective interfaces
 }

优点:

  1. 如果我想在现有接口中添加一个抽象方法,只需更改接口而不必触碰抽象基类。如果我想更改合同,则更改接口和实现类而不触碰基类。

  2. 您可以通过接口为基类提供不可变性。请参阅此 文章

有关更多详细信息,请参阅此相关SE问题:

我应该如何解释接口和抽象类之间的区别?


1
使用接口来强制执行跨不相关类族的合同。例如,您可能会为表示集合的类具有共同的访问方法,但包含根本不同的数据,即一个类可能表示查询结果集,而另一个类可能表示图库中的图像。此外,您可以实现多个接口,从而允许您混合(并表示)类的功能。
当类具有共同关系并且因此具有相似的结构和行为特征时,请使用继承,例如汽车、摩托车、卡车和SUV都是道路车辆的类型,可能包含多个轮子和最高速度。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接