题 接口与抽象类(通用OO)


我最近接受过两次电话采访,我被问及接口和抽象类之间的区别。我已经解释了我能想到的每一个方面,但似乎他们在等我提一些具体的东西,我不知道它是什么。

根据我的经验,我认为以下是正确的。如果我错过了重点,请告诉我。

接口:

在接口中声明的每个单独的方法都必须在子类中实现。 接口中只能存在事件,代理,属性(C#)和方法。一个类可以实现多个接口。

抽象类:

只有抽象方法必须由子类实现。 Abstract类可以有实现的常规方法。 Abstract类还可以在Events,Delegates,Properties和Methods旁边有类变量。由于C#中不存在多重继承,因此类只能实现一个抽象类。

  1. 毕竟,面试官提出了一个问题“如果你有一个只有抽象方法的抽象类怎么办?那么它与界面会有什么不同?”我不知道答案,但我认为这是上面提到的继承权吗?

  2. 另一位采访者问我,如果你在界面中有一个Public变量,那么它与Abstract Class有什么不同?我坚持认为你不能在界面中有一个公共变量。我不知道他想听到什么,但他也不满意。

也可以看看


1198
2018-04-17 16:42


起源


虽然我认为了解两者之间的区别很重要,但这不是一个好的面试问题,imo。除非这份工作是写一本关于OO主题的书。你最好不要为那些叮当蝙蝠工作。 - Alan
@Alan:我实际上喜欢这个作为一个面试问题,但我不会这样对待它 - 我可能更喜欢发布它“在定义层次结构时,你会选择一个抽象基类的接口? “,或类似的东西。 - Reed Copsey
也许他们追求的是一个更注重设计的答案......虽然和你一样,我会把它当作一个技术问题。 - CurtainDog
这里有很好的表格差异: mindprod.com/jgloss/interfacevsabstract.html - Rajat_R
@Kave: I insisted you can't have a public variable inside an interface. 我认为界面可以有公共变量。事实上,界面中的变量是自动公开的和最终的。 - a Learner


答案:


虽然你的问题表明它是“普通的OO”,但它似乎真正关注.NET对这些术语的使用。

在.NET中(类似于Java):

  • 接口可以没有状态或实现
  • 实现接口的类必须提供该接口的所有方法的实现
  • 抽象类可能包含状态(数据成员)和/或实现(方法)
  • 可以在不实现抽象方法的情况下继承抽象类(尽管这样的派生类本身就是抽象的)
  • 接口可能是多重继承的,抽象类可能不是(这可能是接口与abtract类分开存在的关键具体原因 - 它们允许实现多重继承,从而消除了一般MI的许多问题)。

作为一般的OO术语,差异不一定是明确定义的。例如,有些C ++程序员可能持有类似的严格定义(接口是抽象类的严格子集,不能包含实现),而有些人可能会说具有一些默认实现的抽象类仍然是接口或非抽象类class仍然可以定义一个接口。

实际上,有一种称为非虚拟接口(NVI)的C ++习惯用法,其中公共方法是非虚拟方法,可以“窃取”私有虚拟方法:


632
2018-04-17 17:18



谢谢。我想,既然你的回答提到了状态+对所有休息的一个很好的概述,我将你的回答标记为最终答案。你是对的我要求一般OO,因为我的第一个面试官要求一般OO,但由于我是一个C#家伙,我倾向于忘记这一点。 ;-)同样感谢C ++的解释,因为c ++总是令人兴奋。 - Houman
我认为Michael提供的解释中的一个关键点是,在实现接口时,您必须实现接口中的所有成员,但是当从抽象类继承时,子类不需要实现其父成员 - Guillermo Gomez
+1:我愿意打赌那些主持面试的猴子甚至没有意识到其他语言实现OO的方式不同。 - Lightness Races in Orbit
@JL我看不出问题出在哪里。您似乎将抽象方法与抽象类混淆了。抽象 方法 没有实施。但是,在摘要里面 类, 一些 方法可以是抽象的(即没有实现),而其他方法确实可以实现。 - xji
请注意,在Java 8中,您现在可以在接口中使用默认方法和静态方法,这意味着Java接口可以具有实现。参考 这里。显然你主要提到.NET,所以这只是一个关于Java的观察。 - davtom


怎么样比喻:当我在空军时,我去了飞行员训练,并成为美国空军(美国空军)的飞行员。那时我没有资格飞行任何东西,不得不参加飞机型训练。一旦我获得资格,我就是一名飞行员(抽象班)和一名C-141飞行员(具体班级)。在我的一项任务中,我获得了额外职责:安全官。现在我仍然是一名飞行员和一名C-141飞行员,但我也履行了安全官职责(我实施了ISafetyOfficer,可以这么说)。一名飞行员不需要担任安全官,其他人也可以这样做。

所有美国空军飞行员都必须遵守某些空军规定,所有C-141(或F-16或T-38)飞行员都是'美国空军飞行员'。任何人都可以成为安全官。所以,总结一下:

  • 飞行员:抽象课
  • C-141飞行员:具体课程
  • ISafety Officer:界面

补充说明:这是一个类比,以帮助解释概念,而不是编码建议。请参阅下面的各种评论,讨论很有意思。


756
2017-08-30 04:40



我真的很喜欢这个比喻,它用一个简单的例子来解释一个稍微复杂的话题 - Kevin Bowersox
这是理解复杂OO术语的最佳方式。简而言之,只有当你能够实际使用它时,所有的理论都是值得的。 @Jay你反过来很容易掌握几个子弹点(大多是穿透思维而不是被吸收!) - v s
我还是有点困惑。说,你现在有F-16和T-38资格,所以现在上课 Jay 不能从多个类继承(C-141 pilot,F-16 pilot和T-38 pilot),这是否意味着哪个类应该成为接口?谢谢 - Alex Okrushko
很多人都给Alex的评论+1,因为它揭示了这个例子中的一些弱点。首先,我会说杰伊将是C-141Pilot的一个实例,而不是它自己的类。此外,由于在美国空军,99%的飞行员一次只能在一架飞机上获得资格(FCF和试飞员是明显的例外)我没有考虑多种资格以及如何实施。据我所知,50年前的飞行员同时获得了25架不同飞机的资格,我认为这说明了我们不想使用多重继承。 - Jay
由于一名飞行员不太可能一次飞越多架飞机,因此这将是实施战略模式的好机会。 Pilot将拥有一组认证,并在运行时选择正确的认证。通过TakeOff,Land,Eject方法,认证将被编码为实现IFlyPlane接口的行为。 - Michael Blackburn


我认为他们正在寻找的答案是基本或OPPS的哲学差异。

当派生类共享抽象类的核心属性和行为时,将使用抽象类继承。实际定义类的行为。

另一方面,当类共享外围行为时,使用接口继承,而不必定义派生类。

例如。汽车和卡车共享汽车抽象类的许多核心属性和行为,但他们也分享一些外围行为,如生成排气,甚至非汽车类如钻孔机或PowerGenerators共享,并不一定定义汽车或卡车,所以Car,Truck,Driller和PowerGenerator都可以共享IExhaust相同的界面。


194
2017-08-31 12:09



我认为更好的类比将是“usesFuel”,它会显示出来 合同 界面的本质。 - Pureferret
@Pureferret如果 accelerate 是汽车抽象类的核心行为的一部分,那么我不能说 accelerate 显示了 合同 性质。什么是合同性质?为什么这个词 contract 每当我们谈论时都会介绍 interface? - overexchange
@overexchange因为通常是接口 只是 两个“表面”相遇的地方,但合同一词意味着有一个协议 怎么样 两个“表面”相遇。生产废气是你同意的事情,这对我来说是没有意义的(至少对我而言)。但是(对我来说)你可以同意需要使用燃料是有意义的。 - Pureferret
@Pureferret我提出了一个查询 链接 对于相同的 - overexchange
@Pureferret如果 interface 需要有外围行为,那么为什么呢 public interface List<E> extends Collection<E> {} 旨在描述的核心行为 list?这实际上与prasun的答案相矛盾。都 Collection<E> 和 List<E> 是这里的接口。 - overexchange


简短:抽象类用于 造型 类似外观类的类层次结构(例如,Animal可以是抽象类,Human,Lion,Tiger可以是具体的派生类)

接口用于 通讯 在2个相似/不相似的类之间,它们不关心实现接口的类的类型(例如,Height可以是接口属性,它可以由Human,Building,Tree实现。如果你可以吃,无所谓你可以游泳你可能死亡或任何事情......只有你需要有高度(在你的班级实施))。


163
2018-04-05 09:53



我真的很喜欢这个答案,因为有时很难通过查看更抽象的东西来回答“事物”之间的不同之处 意图而不仅仅是 结构体 (从结构上讲,接口和纯抽象类几乎是一样的)。 - LostSalad
枚举抽象类和接口在特定语言中可以做什么很容易,但是创建抽象来赋予对象意义和责任更加困难,而你所说的完全恢复在OO中使用2概念。谢谢! - Samuel
我认为这是面试问题的最佳答案:)。 - Envil
@dhananjay:我看到Height如何与Animal类的概念分开,可以来自另一个不同的类,但你究竟是什么意思通过类之间的“沟通”?它只是为自己的类定义高度,对吗? - T.T.T.


还有其他一些差异 -

接口不能具有任何具体实现。抽象基类可以。这允许您在那里提供具体的实现。这可以允许抽象基类实际提供更严格的契约,而界面实际上只描述了如何使用类。 (抽象基类可以具有定义行为的非虚拟成员,从而为基类作者提供更多控制。)

可以在类上实现多个接口。类只能从单个抽象基类派生。这允许使用接口的多态层次结构,但不允许抽象基类。这也允许使用接口进行伪多继承。

可以在v2 +中修改抽象基类,而不会破坏API。对接口的更改正在发生变化。

[C#/ .NET特定]接口与抽象基类不同,可以应用于值类型(结构)。结构不能从抽象基类继承。这允许行为合同/使用指南应用于值类型。


74
2018-04-17 16:46



+ 1表示可以在类上实现多个接口的关键点。 - cgp
对于抽象基类IMO的接口来说,这是一个真正的优势。否则,我同意.NET设计指南,现在说“更喜欢接口上的抽象基类” - Reed Copsey
虽然,如果您能够添加点,它也可以应用于任何类,那将是非常热衷的。 - cgp
@altCognito:用第二段处理的图形。但这确实提醒我,接口可以处理值类型,所以我补充说。 - Reed Copsey
非常感谢您的确切描述。这确实非常有帮助。我刚来这地方。遗憾的是你不能选择两个回答作为“答案”。令我困惑的一件事是你对Abstract'base'类的使用。所有抽象类都是子类的基类。为什么要额外命名'基础'? - Houman


遗产
考虑一辆汽车和一辆公共汽车。它们是两种不同的载体。但是,他们仍然有一些共同的特性,比如他们有转向,刹车,齿轮,发动机等。
所以使用继承概念,这可以表示如下......

public class Vehicle {
    private Driver driver;
    private Seat[] seatArray; //In java and most of the Object Oriented Programming(OOP) languages, square brackets are used to denote arrays(Collections).
    //You can define as many properties as you want here ...
}

现在一辆自行车......

public class Bicycle extends Vehicle {
    //You define properties which are unique to bicycles here ...
    private Pedal pedal;
}

还有一辆车......

public class Car extends Vehicle {
    private Engine engine;
    private Door[] doors;
}

这就是全部 遗产。我们使用它们将对象分类为更简单的Base形式及其子代,如上所述。

抽象类

抽象类是 残缺 对象。为了进一步理解,让我们再次考虑车辆类比。
可以驾驶车辆。对?但是不同的车辆以不同的方式驾驶......例如,驾驶自行车时不能驾驶汽车。
那么如何表示车辆的驱动功能呢?更难以检查它是什么类型的车辆并用它自己的功能驱动它;添加新型车辆时,您必须反复更改Driver类。
这里有抽象类和方法的作用。您可以将驱动器方法定义为抽象,以告知每个继承子项必须实现此功能。
所以,如果你修改车辆类......

//......Code of Vehicle Class
abstract public void drive();
//.....Code continues

自行车和汽车还必须指定如何驾驶它。否则,代码将无法编译并引发错误。
简而言之,抽象类是一个部分不完整的类,其中包含一些不完整的函数,继承的子函数必须指定它们自己的函数。

接口 接口完全不完整。他们没有任何财产。他们只是表明继承的孩子能够做某事......
假设您有不同类型的手机。他们每个人都有不同的方式来做不同的功能;例如:打电话给一个人。手机制造商指定了如何做到这一点。在这里,手机可以拨打一个号码 - 也就是说,它是可拨号的。让我们将其表示为一个界面。

public interface Dialable {
    public void dial(Number n);
}

这里Dialable的制造商定义了如何拨号。您只需要拨打一个号码即可拨号。

// Makers define how exactly dialable work inside.

Dialable PHONE1 = new Dialable() {
    public void dial(Number n) {
        //Do the phone1's own way to dial a number
    }
}

Dialable PHONE2 = new Dialable() {
    public void dial(Number n) {
        //Do the phone2's own way to dial a number
    }
}


//Suppose there is a function written by someone else, which expects a Dialable
......
public static void main(String[] args) {
    Dialable myDialable = SomeLibrary.PHONE1;
    SomeOtherLibrary.doSomethingUsingADialable(myDialable);
}
.....

因此使用接口而不是抽象类,使用Dialable的函数的编写者不必担心它的属性。例如:它是否有触摸屏或拨号盘,是固定固定电话还是手机。你只需要知道它是否可以录音;它是否继承(或实现)Dialable接口。

更重要的是,如果有一天你用另一个切换Dialable

......
public static void main(String[] args) {
    Dialable myDialable = SomeLibrary.PHONE2; // <-- changed from PHONE1 to PHONE2
    SomeOtherLibrary.doSomethingUsingADialable(myDialable);
}
.....

您可以确定代码仍然可以正常工作,因为使用dialable的函数不会(也不能)依赖于Dialable接口中指定的细节以外的细节。它们都实现了Dialable接口,这是函数唯一关心的。

开发人员通常使用接口来确保对象之间的互操作性(可互换使用),只要它们共享一个共同的功能(就像您可能更改为固定电话或移动电话一样,只需要拨打一个号码)。简而言之,接口是一个更简单的抽象类版本,没有任何属性。
另外,请注意您可以根据需要实现(继承)任意数量的接口,但您只能扩展(继承)单个父类。

更多信息 抽象类与接口 


63
2018-06-14 13:11



“接口没有任何属性”并不是真的。 - Bigeyes
@Bigeyes,java不允许接口中的属性。我认为在其他语言中也是如此。你能解释一下吗? - fz_salam
我指的是C#/ .Net。请看看 例 - Bigeyes
@Bigeyes for C#where接口可以有属性,是不是重新引入了多重继承问题?当一个类使用多个定义了相同属性的接口时会发生什么?只是好奇的谢谢 - stackPusher
@ T.T.T。:刚刚添加了一个例子。请看一看。 - fz_salam


如果你考虑 java 作为回答这个问题的OOP语言,Java 8发布导致上述答案中的一些内容已经过时。现在java接口可以有默认方法和具体实现。

神谕 网站 提供了之间的关键差异 interface 和 abstract 类。

考虑使用抽象类 如果:

  1. 您希望在几个密切相关的类之间共享代码。
  2. 您希望扩展抽象类的类具有许多常用方法或字段,或者需要除公共之外的访问修饰符(例如protected和private)。
  3. 您想声明非静态或非最终字段。

考虑使用接口 如果:

  1. 您希望不相关的类将实现您的接口。例如,许多不相关的对象可以实现 Serializable 接口。
  2. 您希望指定特定数据类型的行为,但不关心谁实现其行为。
  3. 您希望利用类型的多重继承。

简单来说,我想用

接口: 通过多个不相关的对象实现合同

抽象类: 在多个相关对象之间实现相同或不同的行为

看看代码示例,以清楚的方式理解事物: 我该如何解释Interface和Abstract类之间的区别?


33
2017-11-27 19:20