ChatGPT解决这个技术问题 Extra ChatGPT

接口 vs 抽象类(通用 OO)

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

根据我的经验,我认为以下是正确的。如果我遗漏了一个要点,请告诉我。

界面:

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

抽象类:

只有抽象方法必须由子类实现。抽象类可以具有带有实现的普通方法。除了事件、委托、属性和方法之外,抽象类还可以具有类变量。由于 C# 中不存在多重继承,一个类只能实现一个抽象类。

毕竟,面试官提出了一个问题“如果你有一个只有抽象方法的抽象类怎么办?这与接口有什么不同?”我不知道答案,但我认为这是上面提到的继承对吗?另一位面试官问我,如果你在接口中有一个公共变量,那与抽象类有什么不同?我坚持你不能在接口内有一个公共变量。我不知道他想听什么,但他也不满意。

也可以看看:

何时使用接口而不是抽象类,反之亦然

接口与抽象类

您如何决定使用抽象类和接口?

接口和抽象类有什么区别?

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

m
mozzbozz

打个比方吧:当我在空军时,我参加了飞行员培训并成为了 USAF(美国空军)飞行员。那时我没有资格驾驶任何东西,必须参加飞机类型培训。一旦我获得资格,我就是一名飞行员(抽象类)和一名 C-141 飞行员(具体类)。在我的一项任务中,我被赋予了一项额外的职责:安全官。现在我仍然是一名飞行员和一名 C-141 飞行员,但我也履行了安全官的职责(可以这么说,我实施了 ISafetyOfficer)。飞行员不需要成为安全官,其他人也可以这样做。

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

Pilot:抽象类

C-141 飞行员:混凝土级

I安全官:界面

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


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

虽然您的问题表明它是针对“一般 OO”的,但它似乎确实专注于 .NET 对这些术语的使用。

在 .NET 中(Java 类似):

接口可以没有状态或实现

实现接口的类必须提供该接口所有方法的实现

抽象类可能包含状态(数据成员)和/或实现(方法)

可以在不实现抽象方法的情况下继承抽象类(尽管这样的派生类本身就是抽象的)

接口可能是多重继承的,抽象类可能不是(这可能是接口与抽象类分开存在的关键具体原因——它们允许实现多重继承,从而消除了一般 MI 的许多问题)。

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

确实,有一个称为非虚拟接口 (NVI) 的 C++ 习语,其中公共方法是非虚拟方法,它们“转换”到私有虚拟方法:

http://www.gotw.ca/publications/mill18.htm

http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-Virtual_Interface


谢谢你。我认为由于您的回答提到了状态+对所有休息的良好概述,因此我将您的回答标记为最终答案。你说得对,我问的是通用 OO,因为我的第一个面试官问的是通用 OO,但由于我是 C# 人,我倾向于忘记这一点。 ;-) 还要感谢 C++ 的解释,因为 C++ 总是令人兴奋。
我认为迈克尔提供的解释中的一个关键点是,在实现接口时,您必须实现接口中的所有成员,但是当从抽象类继承时,子类不需要实现其父类的成员
+1:我敢打赌,那些主持采访的猴子甚至没有意识到其他语言以不同的方式实现 OO。
请注意,在 Java 8 中,您现在可以在接口中使用默认方法和静态方法,这意味着 Java 接口可以具有实现。参考 here。显然,您主要指的是 .NET,所以这只是对 Java 的观察。
我知道这很老了,但是 C# 中接口和抽象类之间的一个非常重要的区别是,一旦你发布了一个接口,更改接口是一个重大更改,但对于抽象类而言并非如此。
P
Prasun

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

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

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

例如。 Car 和 Truck 共享汽车抽象类的许多核心属性和行为,但它们也共享一些外围行为,例如生成排气,即使是 Drillers 或 PowerGenerators 等非汽车类也共享,并且不一定定义 Car 或 Truck ,所以 Car、Truck、Driller 和 PowerGenerator 都可以共享同一个接口 IExhaust。


我认为更好的类比是“usesFuel”,它可以显示接口的合同性质。
@Pureferret 如果 accelerate 是汽车抽象类核心行为的一部分,那么我不能说 accelerate 显示了 contract 的性质。什么是合同性质?为什么每当我们谈论 interface 时都会引入这个词 contract
@overexchange,因为通常界面就是两个“表面”相遇的地方,但是合同这个词意味着就两个“表面”如何相遇达成了一致。产生废气是您“同意”的事情(至少对我而言)是没有意义的。但是(对我来说)你可以同意需要使用燃料是有道理的。
@Pureferret 我在 link 提出了同样的问题
@Pureferret 如果 interface 需要具有外围行为,那么为什么 public interface List<E> extends Collection<E> {} 旨在描述 list 的核心行为?这实际上与 prasun 的回答相矛盾。 Collection<E>List<E> 都是这里的接口。
D
Dhananjay

简短:抽象类用于对外观相似的类的类层次结构进行建模(例如,Animal 可以是抽象类,Human、Lion、Tiger 可以是具体的派生类)

接口用于两个相似/不相似的类之间的通信,它不关心实现接口的类的类型(例如高度可以是接口属性,它可以由 Human , Building , Tree 实现。不管你能不能吃,你可以游泳,你可以死或任何事情..重要的是你需要有高度(在你的课堂上实现))。


我真的很喜欢这个答案,因为有时很难通过查看更抽象的东西(例如意图)来回答事物之间的“什么”不同,而不仅仅是结构(因为在结构上,接口和纯抽象类几乎相同事物)。
很容易列举抽象类与接口在特定语言中可以做什么,但是创建一个抽象来赋予对象意义和责任则更加困难,而你所说的完全恢复了 OO 中 2 概念的使用。谢谢!
@dhananjay:我看到 Height 如何与 Animal 类的概念分开,并且可以来自另一个不同的类,但是类之间的“通信”到底是什么意思?它只是为自己的类定义高度,对吗?
我真的很喜欢这个答案。不要指望我理解空军的类比。因为我是人,所以效果很好:)
R
Reed Copsey

还有一些其他差异 -

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

一个类可以实现多个接口。一个类只能派生自一个抽象基类。这允许使用接口而不是抽象基类的多态层次结构。这也允许使用接口进行伪多继承。

抽象基类可以在 v2+ 中进行修改,而不会破坏 API。对接口的更改是重大更改。

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


+1 表示可以在一个类上实现多个接口的关键点。
这是接口优于抽象基类的一个真正优势,IMO。否则,我同意 .NET 设计指南,现在说“更喜欢抽象基类而不是接口”
虽然,如果您可以添加一点,即它的接口也可以应用于任何类,那将是非常棒的。
@altCognito:认为这是在第二段中处理的。不过,这确实提醒了我,接口适用于值类型,所以我添加了这一点。
非常感谢您的准确描述。这确实很有帮助。我刚来这地方。很遗憾您不能选择两个响应作为“答案”。让我感到困惑的一件事是您对抽象“基”类的使用。所有抽象类都是子类的基类。为什么要额外命名“基础”?
C
Community

继承考虑一辆汽车和一辆公共汽车。它们是两种不同的车辆。但是,它们仍然具有一些共同的属性,例如它们具有转向、制动器、齿轮、发动机等。因此,使用继承概念,这可以表示为以下...

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;
}

这就是继承。如上所示,我们使用它们将对象分类为更简单的基本形式及其子形式。

抽象类

抽象类是不完整的对象。为了进一步理解它,让我们再次考虑车辆类比。可以驾驶车辆。正确的?但是不同的车辆以不同的方式驾驶......例如,您不能像驾驶自行车一样驾驶汽车。那么如何表示车辆的驱动功能呢?更难检查它是什么类型的车辆并用它自己的功能来驱动它;添加新类型的车辆时,您将不得不一次又一次地更改 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 接口,这是该函数唯一关心的事情。

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

更多信息 Abstract classes vs Interfaces


“接口没有任何属性”是不正确的。
@Bigeyes,java 不允许接口中的属性。我认为其他语言也一样。你能解释一下吗?
我指的是 C#/.Net。请参阅example
接口可以具有属性的 C# 的 @Bigeyes,这不是重新引入了多重继承问题吗?当一个类使用定义了相同属性的多个接口时会发生什么?只是好奇谢谢
@happycoder:回复:“这里通过使用接口而不是抽象类,您不必担心它的属性。例如:它是否有触摸屏或拨号盘,是固定座机还是手机。您只需要知道它是否可拨号;它是否继承(或实现)了 Dialable 接口。” - 你能在代码示例中显示这个吗,也没有看到它是如何被继承的......
C
Community

如果您将 java 视为 OOP 语言来回答此问题,Java 8 版本会导致上述答案中的某些内容已过时。现在java接口可以有带有具体实现的默认方法。

Oracle website 提供了 interfaceabstract 类之间的主要区别。

在以下情况下考虑使用抽象类:

您希望在几个密切相关的类之间共享代码。您希望扩展抽象类的类具有许多公共方法或字段,或者需要公共以外的访问修饰符(例如受保护和私有)。您要声明非静态或非最终字段。

在以下情况下考虑使用接口:

您希望不相关的类会实现您的接口。例如,许多不相关的对象可以实现 Serializable 接口。您想指定特定数据类型的行为,但不关心谁实现了它的行为。您想利用类型的多重继承。

简单来说,我想使用

接口:通过多个不相关的对象来实现一个契约

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

查看代码示例以清楚地理解事物:How should I have explained the difference between an Interface and an Abstract class?


@迈克尔布莱克本。
S
Steve Rowe

面试官们在叫着一棵奇怪的树。对于像 C# 和 Java 这样的语言,是有区别的,但在像 C++ 这样的其他语言中则没有。 OO 理论并没有区分两者,只是语言的语法。

抽象类是具有将被继承的实现和接口(纯虚方法)的类。接口通常没有任何实现,只有纯虚函数。

在 C# 或 Java 中,没有任何实现的抽象类与接口的区别仅在于用于从接口继承的语法以及您只能从接口继承的事实。


一周前我被问到同样的问题,我没有使用 Java 的经验,但我已经使用 C++ 有一段时间了。面试官在问问题之前没有指定语言,所以我只是解释了这种情况下的接口是抽象类,没有任何状态或任何类型的实现。我同意这也是一个奇怪的问题。
T
TheTXI

通过实现接口,您正在实现组合(“has-a”关系)而不是继承(“is-a”关系)。当涉及到需要使用接口来实现行为组合而不是继承的设计模式时,这是一个需要记住的重要原则。


IMO,接口实现了更多的“作为一个”关系。封装比接口更好地实现组合。
我不认为实现接口会受到组合。
另外,界面更可能用于描述“能力”,例如 IDisposable。它用于在类之间共享这些类“能够做”某事的功能。更多示例 IFlyable 可以通过鸟和飞机来实现。但是 Bird 可能源自 Class Creature,而飞机源自 AirCraft。
F
Farzad Karimi

从概念上讲,保持语言特定的实现、规则、好处并通过使用任何一个或两者来实现任何编程目标,可以或不能有代码/数据/属性,等等等等,单继承或多继承,都放在一边

1-抽象(或纯抽象)类旨在实现层次结构。如果您的业务对象在结构上看起来有些相似,仅表示父子(层次结构)类型的关系,那么将使用继承/抽象类。如果您的业务模型没有层次结构,则不应使用继承(这里我不是在谈论编程逻辑,例如某些设计模式需要继承)。从概念上讲,抽象类是OOP中实现业务模型层次结构的一种方法,它与接口无关,实际上将抽象类与接口进行比较是没有意义的,因为两者在概念上是完全不同的东西,面试时被问到只是为了检查概念,因为它看起来在实现方面都提供了一些相同的功能,而且我们程序员通常更强调编码。 [请记住,抽象与抽象类不同]。

2-接口是一个契约,一个完整的业务功能,由一组或多组功能表示。这就是为什么它被实现而不是继承。业务对象(无论是否属于层次结构)可以具有任意数量的完整业务功能。它与抽象类无关,通常意味着继承。比如人会跑,大象会跑,鸟会跑,等等,所有这些不同层次的对象都会实现RUN接口或者EAT或SPEAK接口。不要进入实现,因为您可能会将其实现为具有实现这些接口的每种类型的抽象类。任何层次结构的对象都可以具有与其层次结构无关的功能(接口)。

我相信,接口不是为了实现多重继承或公开公共行为而发明的,同样,纯抽象类不是为了推翻接口,而是接口是对象可以执行的功能(通过该接口的函数),抽象类代表一个层次结构的父级,以产生具有父级核心结构(属性+功能)的子级

当您被问及差异时,除非明确询问,否则实际上是概念上的差异而不是特定于语言的实现的差异。

我相信,两位面试官都期望这两者之间有一条直接的区别,当你失败时,他们试图通过将 ONE 作为 OTHER 来推动你走向这种区别

如果你有一个只有抽象方法的抽象类怎么办?


这几乎很好地总结了这个问题的答案。
实现的功能与扩展的结构,很好!
J
JegsVala

我将解释接口和抽象类的深度细节。如果您了解接口和抽象类的概述,那么第一个问题就会出现在您的脑海中,什么时候应该使用接口,什么时候应该使用抽象类。所以请查看下面对接口和抽象类的解释。

什么时候应该使用接口?如果您不了解实现,只是我们有需求规范,那么我们使用接口什么时候应该使用抽象类?如果您知道实现但不完全(部分实现),那么我们使用抽象类。默认情况下接口每个方法 public abstract 意味着接口是 100% 纯抽象的。抽象可以有具体方法和抽象方法,什么是具体方法,在抽象类中有实现,抽象类是声明为抽象的类——它可能包含也可能不包含抽象方法。接口 我们不能将接口声明为私有的、受保护的 Q. 为什么我们不将接口声明为私有且受保护的?因为默认接口方法是公共抽象的,所以我们没有将接口声明为私有和受保护的。接口方法我们也不能将接口声明为私有的、受保护的、最终的、静态的、同步的、本机的.....我会给出原因:为什么我们不声明同步方法,因为我们不能创建接口对象并且同步是在对象上工作所以我们没有声明同步方法的原因瞬态概念也不适用,因为瞬态与同步工作。 Abstract 我们很乐意与 public,private final static.... 一起使用。意味着在抽象中没有任何限制。接口变量在默认情况下在接口中声明为公共静态最终变量,因此我们也没有将变量声明为私有的、受保护的。易失性修饰符也不适用于接口,因为接口变量默认是公共静态最终和最终变量,一旦将值分配给变量,您就无法更改值,一旦将变量声明到接口,您必须分配变量。易失性变量不断变化,所以它是opp。到 final 这就是我们不在接口中使用 volatile 变量的原因。 Abstract 抽象变量不需要声明为public static final。

我希望这篇文章有用。


我不同意这一点:Abstract class must have at lease one abstract method. 只要您实现它,就可以有一个没有抽象方法的抽象类。参考:An abstract class is a class that is declared abstract—it may or may not include abstract methods. 参考源:docs.oracle.com/javase/tutorial/java/IandI/abstract.html
您正在谈论技术细节和实现,您没有回答一般 OOP 方面的问题
K
K.Miao

这些答案都太长了。

接口用于定义行为。

抽象类用于定义事物本身,包括其行为。这就是为什么我们有时会创建一个抽象类,它具有一些继承接口的额外属性。

这也解释了为什么 Java 只支持类的单继承,而对接口没有限制。因为一个具体的对象不可能是不同的东西,但它可以有不同的行为。


非常简短和简单。如果你能提供一个代码片段,那就太完美了。
C
Charles Bretana

对于.Net,

你对第二个面试官的回答也是对第一个面试官的回答......抽象类可以有实现,AND状态,接口不能......

编辑:另一方面,我什至不会使用短语“子类”(或“继承”短语)来描述“定义为实现”接口的类。对我来说,接口是一个契约的定义,如果一个类被定义为“实现”该接口,则它必须遵守该契约。它不继承任何东西......您必须自己添加所有内容,明确。


是的!状态!这就是第二位面试官用他在界面中说“公共变量”的奇怪方式的意思。天哪!抽象类可以有状态,接口不能!是的,其他所有人也同意他们的继承方式之间的差异,我忘记提及但后来已经弄清楚了。 :) 感谢大家!
不仅仅是状态......抽象类可以有实现。即,它们可以拥有包含实际运行并执行某些操作的代码的方法,这些方法被基类的实例继承和执行......接口不是这样
更重要的是,在某种意义上,抽象类可以被实例化,它们只需要使用派生类定义来实例化,而不是直接实例化。但是抽象类中定义的状态变量在通过新派生类的实例创建的对象中实例化。这个实例是抽象类的一个实例,也是派生类的一个实例——它毕竟是从它派生的。对于接口来说,这些都不是真的。
当您新建定义为实现接口的类的实例时,它不是该接口的“实例”,所有语法都会导致编译器检查该类的代码并确保每个行为(方法,属性, event, eventHandler 等) 由接口定义的,已经在类的代码中实现。
A
Abdallah Gaber

Interface :如果您想对可能相互关联或不相互关联的组件暗示规则,则应使用

优点:

允许多重继承 通过不暴露上下文中使用的确切类型的对象来提供抽象 通过合同的特定签名提供一致性

缺点:

必须实现所有定义的契约不能有变量或委托一旦定义不能改变而不破坏所有类

抽象类:应该用于您希望对彼此相关的组件有一些基本或默认行为或实现的地方

优点:

比接口快 在实现上具有灵活性(可以完全或部分实现) 可以轻松更改而不会破坏派生类

缺点:

无法实例化 不支持多重继承


定义更快。意义重大吗?它甚至意味着什么?在抽象类上调用函数的操作码比在接口上调用函数的操作码快吗?
@denis631 抽象类比接口稍快,因为接口方法中涉及搜索和调用。阅读此coderanch.com/t/503450/java/abstract-class-faster-interface
G
Gnucki

我认为他们不喜欢您的回复,因为您给出了技术差异而不是设计差异。这个问题对我来说就像一个巨魔问题。事实上,接口和抽象类具有完全不同的性质,因此您无法真正比较它们。我会给你我对接口的作用和抽象类的作用的看法。

接口:用于确保类之间的契约和低耦合,以便拥有更可维护、可扩展和可测试的应用程序。

抽象类:仅用于在具有相同职责的类之间分解某些代码。请注意,这是多重继承在 OOP 中不好的主要原因,因为 class shouldn't handle many responsabilities(改用 composition)。

所以接口具有真正的架构作用,而抽象类几乎只是实现的一个细节(当然,如果你正确使用它的话)。


A
Ani

接口:我们不实现(或定义)方法,我们在派生类中这样做。我们不在接口中声明成员变量。接口表达了 HAS-A 关系。这意味着它们是对象的面具。抽象类:我们可以在抽象类中声明和定义方法。我们隐藏它的构造函数。这意味着没有直接从它创建的对象。抽象类可以保存成员变量。派生类继承到抽象类,这意味着派生类的对象没有被屏蔽,它继承到抽象类。这种情况下的关系是 IS-A。

这是我的意见。


A
Aniket Thakur
After all that, the interviewer came up with the question "What if you had an 
Abstract class with only abstract methods? How would that be different
from an interface?" 

Docs 明确表示如果抽象类仅包含抽象方法声明,则应将其声明为接口。

An another interviewer asked me what if you had a Public variable inside
the interface, how would that be different than in Abstract Class?

接口中的变量默认为 public static 和 final。如果抽象类中的所有变量都是公共的,那么问题可能会如何?与接口中的变量不同,它们仍然可以是非静态和非最终的。

最后,我要在上面提到的基础上再补充一点——抽象类仍然是类,属于单一继承树,而接口可以存在于多重继承中。


D
Deepak Mishra

Jeffrey Richter 通过 C# 从 CLR 复制...

我经常听到这样的问题,“我应该设计一个基本类型还是一个接口?”答案并不总是明确的。

以下是一些可能对您有所帮助的指南:

■■ IS-A 与CAN-DO 关系 一个类型只能继承一个实现。如果派生类型不能声明与基类型的 IS-A 关系,则不要使用基类型;使用接口。接口意味着 CAN-DO 关系。如果 CAN-DO 功能似乎属于各种对象类型,请使用接口。例如,类型可以将自身的实例转换为另一种类型(IConvertible),类型可以序列化自身的实例(ISerializable)等。请注意,值类型必须从 System.ValueType 派生,因此不能派生来自任意基类。在这种情况下,您必须使用 CAN-DO 关系并定义接口。

■■ 易用性 作为开发人员,定义从基本类型派生的新类型通常比实现接口的所有方法更容易。基类型可以提供很多功能,因此派生类型可能只需要对其行为进行相对较小的修改。如果您提供接口,则新类型必须实现所有成员。

■■ 一致的实现 无论接口契约的文档记录得多么好,每个人都不太可能100% 正确地实现契约。事实上,COM 遇到了这个问题,这就是为什么某些 COM 对象只能在 Microsoft Word 或 Windows Internet Explorer 中正常工作的原因。通过提供一个具有良好默认实现的基本类型,您可以开始使用一种有效且经过良好测试的类型;然后您可以修改需要修改的部分。

■■ 版本控制 如果将方法添加到基类型,派生类型会继承新方法,您可以开始使用有效的类型,甚至不必重新编译用户的源代码。向接口添加新成员会强制接口的继承者更改其源代码并重新编译。


@AbdullahShoaib is-a 和任何人都可以做但不是可以做,这里有区别。这是最基本的原因,我们需要接口。可以做的行为也将成为 abstract class 的一部分。
j
joelmdev

接口定义了一个服务或一组服务的契约。它们以水平方式提供多态性,因为两个完全不相关的类可以实现相同的接口,但可以互换用作它们实现的接口类型的参数,因为这两个类都承诺满足接口定义的服务集。接口不提供实现细节。

抽象类定义了它的子类的基本结构,以及可选的部分实现。抽象类以垂直但定向的方式提供多态性,因为任何继承抽象类的类都可以被视为该抽象类的实例,但不能反过来。抽象类可以并且经常确实包含实现细节,但不能自行实例化 - 只有它们的子类可以“更新”。

请注意,C# 也允许接口继承。


使用水平和垂直术语可以很清楚地想象出差异。
Z
Ziaullah Khan

tl;博士;当您看到“Is A”关系时,请使用继承/抽象类。当你看到“有”关系时,创建成员变量。当您看到“依赖于外部提供者”时,实现(而不是继承)一个接口。

面试题:接口和抽象类有什么区别?你如何决定何时使用什么?我大多得到以下一个或全部答案: 答案 1:您不能创建抽象类和接口的对象。

ZK(那是我的姓名缩写):你不能创建任何一个对象。所以这没有区别。这是接口和抽象类之间的相似之处。反问:为什么不能创建抽象类或接口的对象?

答案 2:抽象类可以有一个函数体作为部分/默认实现。

ZK:反问:所以如果我把它改成一个纯抽象类,把所有的虚函数都标记为抽象,并且不为任何虚函数提供默认实现。那会使抽象类和接口相同吗?之后它们可以互换使用吗?

答案 3:接口允许多重继承,而抽象类不允许。

ZK:反问:你真的继承自一个接口吗?还是您只是实现一个接口并从抽象类继承?实现和继承有什么区别?这些反题让候选人望而却步,让大多数人摸不着头脑,或者直接跳到下一个问题。这让我觉得人们在面向对象编程的这些基本构建块方面需要帮助。原始问题和所有反问题的答案都可以在英语和 UML 中找到。您必须至少了解以下内容才能更好地理解这两个结构。

普通名词:普通名词是给同一类或同类事物“共同”的名称。例如水果、动物、城市、汽车等。

专有名词:专有名词是物体、地点或事物的名称。苹果、猫、纽约、本田雅阁等。

汽车是普通名词。而本田雅阁是一个专有名词,可能是一个复合专有名词,一个由两个名词组成的专有名词。

来到 UML 部分。您应该熟悉以下关系:

是一个

有个

用途

让我们考虑以下两句话。 - 本田雅阁是汽车吗? - 本田雅阁有车吗?

哪一个听起来正确?简单的英语和理解力。 HondaAccord 和 Cars 共享“Is A”关系。本田雅阁没有汽车。这是辆车。本田雅阁“有一个”音乐播放器。

当两个实体共享“Is A”关系时,它是更好的继承候选者。并且有一个关系是创建成员变量的更好候选者。有了这个,我们的代码看起来像这样:

abstract class Car
{
   string color;
   int speed;
}
class HondaAccord : Car
{
   MusicPlayer musicPlayer;
}

现在本田不生产音乐播放器。或者至少这不是他们的主要业务。

所以他们联系其他公司并签订合同。如果您在此处接收电源并在这两条线上接收输出信号,它将在这些扬声器上正常播放。

这使得音乐播放器成为界面的完美候选者。只要连接工作正常,您不在乎谁为其提供支持。

您可以用索尼或其他方式替换 LG 的 MusicPlayer。它不会改变本田雅阁的任何事情。

为什么不能创建抽象类的对象?

因为你不能走进陈列室说给我一辆车。你必须提供一个专有名词。什么车?可能是本田雅阁。那时销售代理可以为您提供一些东西。

为什么不能创建接口的对象?因为你不能走进陈列室说给我一份音乐播放器的合同。它不会有帮助。接口位于消费者和提供者之间,只是为了促进达成协议。您将如何处理该协议的副本?它不会播放音乐。

为什么接口允许多重继承?

接口不被继承。实现了接口。接口是与外部世界交互的候选者。本田雅阁有一个加油接口。它有给轮胎充气的接口。还有用来给足球充气的软管。所以新代码如下所示:

abstract class Car
{
    string color;
    int speed;
}
class HondaAccord : Car, IInflateAir, IRefueling
{
    MusicPlayer musicPlayer;
}

并且英文会这样写“Honda Accord is a Car 支持充气轮胎和加油”。


m
mcv

大多数答案都集中在抽象类和接口之间的技术差异上,但由于从技术上讲,接口基本上是一种抽象类(没有任何数据或实现),我认为概念上的差异要有趣得多,这可能是什么面试官在后面。

接口协议。它指定:“这就是我们彼此交谈的方式”。它不能有任何实现,因为它不应该有任何实现。这是一份合同。这就像 C 中的 .h 头文件。

抽象类是不完整的实现。一个类可能实现也可能不实现接口,抽象类不必完全实现它。没有任何实现的抽象类有点无用,但完全合法。

基本上任何类,无论是否抽象,都是关于它是什么,而接口是关于你如何使用它。例如:Animal可能是一个抽象类,实现了一些基本的代谢功能,并指定了呼吸和运动的抽象方法而没有给出实现,因为它不知道它是通过鳃呼吸还是通过肺呼吸,以及它是否会飞、会游泳,走路或爬行。另一方面,Mount 可能是一个接口,它指定您可以骑乘动物,而不知道它是什么动物(或者根本不知道它是否是动物!)。

事实上,在幕后,接口基本上是一个只有抽象方法的抽象类,这并不重要。从概念上讲,他们扮演完全不同的角色。


R
Rodia

由于您可能已经从专家那里获得了理论知识,所以我在这里不再赘述,而是让我用一个简单的例子来解释我们可以使用/不能使用InterfaceAbstract class

假设您正在设计一个应用程序来列出 Cars 的所有功能。在各个方面,您需要共同继承,因为 DigitalFuelMeter、空调、座椅调节等某些属性对于所有汽车都是通用的。同样,我们只需要对某些类进行继承,因为制动系统(ABS,EBD)等某些属性仅适用于某些汽车。

以下类作为所有汽车的基类:

public class Cars
{
    public string DigitalFuelMeter()
    {
        return "I have DigitalFuelMeter";
    }

    public string AirCondition()
    {
        return "I have AC";
    }

    public string SeatAdjust()
    {
        return "I can Adjust seat";
    }
}

考虑我们为每个 Cars 设置一个单独的类。

public class Alto : Cars
{
    // Have all the features of Car class    
}

public class Verna : Cars
{
    // Have all the features of Car class + Car need to inherit ABS as the Braking technology feature which is not in Cars        
}

public class Cruze : Cars
{
    // Have all the features of Car class + Car need to inherit EBD as the Braking technology feature which is not in Cars        
}

考虑到我们需要一种方法来继承 Verna 和 Cruze 的制动技术(不适用于 Alto)。虽然两者都使用制动技术,但“技术”是不同的。所以我们正在创建一个抽象类,其中方法将被声明为 Abstract 并且应该在其子类中实现。

public abstract class Brake
{
    public abstract string GetBrakeTechnology();
}

现在我们尝试从这个抽象类继承,制动系统的类型在 Verna 和 Cruze 中实现:

public class Verna : Cars,Brake
{
    public override string GetBrakeTechnology()
    {
        return "I use ABS system for braking";
    }       
}

public class Cruze : Cars,Brake
{
    public override string GetBrakeTechnology()
    {
       return "I use EBD system for braking";
    }         
}

看到上面两个类的问题了吗?它们继承自多个 C#.Net 不允许的类,即使该方法是在子级中实现的。这就需要接口了。

interface IBrakeTechnology
{
    string GetBrakeTechnology();
}

实现如下:

public class Verna : Cars, IBrakeTechnology
{
    public string GetBrakeTechnology()
    {
        return "I use ABS system for braking";
    }
}

public class Cruze : Cars, IBrakeTechnology
{
   public string GetBrakeTechnology()
   {
       return "I use EBD system for braking";
   }        
}

现在 Verna 和 Cruze 可以在 Interface 的帮助下,用自己的制动技术实现多重继承。


由于这些例子,这是最好的解释之一。
这对我来说是有道理的,而不用绞尽脑汁。我只是想为我的学生想出一个汽车的例子。感谢您花时间将这些放在一起。
f
fastcodejava

接口是执行特定行为的轻量级方式。这是一种思考方式。


L
Lightness Races in Orbit

1)一个接口可以看成一个纯抽象类,是一样的,但是尽管如此,实现一个接口和从一个抽象类继承是不一样的。当你从这个纯抽象类继承时,你定义了一个层次结构 -> 继承,如果你实现了你没有实现的接口,你可以实现任意数量的接口,但你只能从一个类继承。

2)您可以在接口中定义属性,因此实现该接口的类必须具有该属性。

例如:

  public interface IVariable
  {
      string name {get; set;}
  }

实现该接口的类必须具有这样的属性。


o
ouflak

虽然这个问题已经很老了,但我想补充一点支持接口:

可以使用任何依赖注入工具注入接口,而抽象类注入很少支持。


我相信您的意思是 DI 工具可以注入一个实现接口的类。一些这样的工具还可以注入从抽象类派生的类,或者你是说这是不可能的?
C
Community

another answer of mine 开始,主要处理何时使用一个与另一个:

根据我的经验,当您有多个类,每个类都需要响应相同的方法或方法时,最好使用接口,以便它们可以被其他代码互换使用,这些代码将针对这些类的公共接口编写。接口的最佳用途是当协议很重要但每个类的底层逻辑可能不同时。如果您要复制逻辑,请考虑使用抽象类或标准类继承。


x
x19

接口类型与抽象基类

改编自 Pro C# 5.0 and the .NET 4.5 Framework 书。

接口类型可能看起来非常类似于抽象基类。回想一下,当一个类被标记为抽象时,它可以定义任意数量的抽象成员来为所有派生类型提供多态接口。然而,即使一个类确实定义了一组抽象成员,它也可以自由定义任意数量的构造函数、字段数据、非抽象成员(带有实现)等等。另一方面,接口只包含抽象成员定义。由抽象父类建立的多态接口受到一个主要限制,即只有派生类型支持抽象父类定义的成员。但是,在较大的软件系统中,开发多个类层次结构非常常见,这些层次结构除了 System.Object 之外没有共同的父级。鉴于抽象基类中的抽象成员仅适用于派生类型,我们无法配置不同层次结构中的类型以支持相同的多态接口。例如,假设您定义了以下抽象类:

public abstract class CloneableType
{
// Only derived types can support this
// "polymorphic interface." Classes in other
// hierarchies have no access to this abstract
// member.
   public abstract object Clone();
}

鉴于此定义,只有扩展 CloneableType 的成员才能支持 Clone() 方法。如果您创建一组不扩展此基类的新类,则无法获得此多态接口。此外,您可能还记得 C# 不支持类的多重继承。因此,如果您想创建一个既是 Car 又是 CloneableType 的 MiniVan,您无法这样做:

// Nope! Multiple inheritance is not possible in C#
// for classes.
public class MiniVan : Car, CloneableType
{
}

正如您所猜测的那样,接口类型来拯救。定义接口后,它可以由任何类或结构、任何层次结构、任何命名空间或任何程序集(用任何 .NET 编程语言编写)实现。如您所见,接口是高度多态的。考虑在 System 命名空间中定义的名为 ICloneable 的标准 .NET 接口。该接口定义了一个名为 Clone() 的方法:

public interface ICloneable
{
object Clone();
}

R
Ravindra babu

回答第二个问题:interface 中定义的 public 变量默认为 static final,而 abstract 类中的 public 变量是实例变量。


F
Felypp Oliveira

当然,理解 OOP 中接口和抽象类的行为(以及语言如何处理它们)很重要,但我认为理解每个术语的确切含义也很重要。您能想象 if 命令不完全按照术语的含义工作吗?此外,实际上有些语言正在减少,甚至更多,接口和抽象之间的差异......如果有一天这两个术语的操作几乎相同,至少你可以定义自己应该在哪里(以及为什么)它们中的任何一个用于。

如果您阅读一些字典和其他字体,您可能会发现同一术语的不同含义但有一些共同的定义。我认为我在this site中找到的这两个含义非常非常好并且合适。

界面:

使独立的、有时是不相容的元素能够有效协调的事物或情况。

抽象的:

某种东西,它本身集中了任何更广泛或更普遍的东西或几样东西的基本品质;本质。

例子:

你买了一辆车,它需要燃料。

https://i.stack.imgur.com/2zvSS.png

您的汽车模型是 XYZ,属于 ABC 类型,因此它是一辆具体的汽车,是汽车的特定实例。汽车不是真实的物体。实际上,它是创建特定对象的一组抽象标准(质量)。简而言之,Car 是一个抽象类,它是“在自身中集中了任何更广泛或更一般事物的基本品质的东西”

应使用唯一符合汽车手册规格的燃料来填充汽车油箱。实际上,没有什么可以限制您添加任何燃料,但发动机只有使用指定的燃料才能正常工作,因此最好遵循其要求。要求表明,它与同类型 ABC 的其他汽车一样接受一组标准燃料。

在面向对象的视图中,类型 ABC 的燃料不应被声明为一个类,因为那里没有特定类型的汽车的具体燃料。尽管您的汽车可以接受抽象类 Fuel 或 VehicularFuel,但您必须记住,您只有一些现有的车辆燃料符合规范,即那些实现汽车手册中要求的燃料。简而言之,他们应该实现接口 ABCGenreFuel,它“...使单独的、有时不兼容的元素能够有效地协调”

附录

另外,我认为您应该记住术语类的含义,即(来自前面提到的同一站点):

班级:

由于共同的属性、特征、品质或特征而被视为组成一个群体的若干人或事物;种类;

这样,一个类(或抽象类)不应该只代表公共属性(如接口),而是某种具有公共属性的组。接口不需要代表一种。它必须代表共同的属性。这样,我认为类和抽象类可以用来表示不应该经常改变其方面的事物,比如人类是哺乳动物,因为它代表了一些种类。种类不应该经常改变自己。


太多的绒毛,不要让它听起来比现在更令人困惑。
j
josliber

从编码的角度

如果抽象类只有抽象方法,则接口可以替换抽象类。否则将抽象类更改为接口意味着您将失去继承提供的代码可重用性。

从设计角度

如果它是“是”关系并且您需要一个子集或所有功能,请将其保留为抽象类。如果它是“应该做”的关系,请将其保留为接口。

决定你需要什么:只是策略执行,或代码可重用性和策略。