接口vs抽象类(通用OO)


1413

最近,我接受了两次电话采访,有人问我接口和抽象类之间的区别。我已经解释了我可以想到的各个方面,但是似乎他们正在等我提及一些特定的内容,我不知道它是什么。

根据我的经验,我认为以下是对的。如果我缺少要点,请告诉我。

接口:

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

抽象类:

子类仅必须实现Abstract方法。Abstract类可以具有实现的常规方法。抽象类在事件,委托,属性和方法旁边也可以具有类变量。由于C#中不存在多重继承,一个类只能实现一个抽象类。

  1. 毕竟,面试官提出了一个问题:“如果您有一个仅包含抽象方法的Abstract类,该怎么办?与接口有什么不同?” 我不知道答案,但我认为这是上面提到的继承对吗?

  2. 另一个面试官问我,如果接口内有一个Public变量,那和Abstract Class有什么不同?我坚持认为,接口内不能有公共变量。我不知道他想听什么,但他也不满意。

另请参阅


412
尽管我认为了解两者之间的差异很重要,但imo并不是一个很好的面试问题。除非工作是写关于面向对象的书。您最好不要为那些叮当声工作。
艾伦(Alan)2009年

107
@Alan:我实际上很喜欢将其作为面试问题,但我不会对这种人产生疑问-我可能会更喜欢发布它,例如“在定义层次结构时,您将在抽象基类上选择一个接口吗? ”或类似的内容。
Reed Copsey

11
也许他们是在追求更专注于设计的答案之后……尽管像您一样,我会将其视为技术问题。
CurtainDog 2010年

16
此处的表格格式差异不错:mindprod.com/jgloss/interfacevsabstract.html
Rajat_R 2013年

30
@Kave:I insisted you can't have a public variable inside an interface.我认为接口可以具有公共变量。实际上,接口中的变量是自动公开的和最终的。
的学习者

Answers:


746

尽管您的问题表明这是“通用OO”的意思,但实际上它似乎专注于.NET对这些术语的使用。

在.NET(与Java类似)中:

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

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

确实,有一个称为非虚拟接口(NVI)的C ++习惯用法,其中公共方法是“虚化”为私有虚拟方法的非虚拟方法:


7
谢谢。我认为,既然您的回答提到了状态+所有休息的简要概述,我将您的回答标记为最终答案。您是对的,因为我的第一位面试官要求进行一般OO,所以我要求进行一般OO,但是由于我是C#人员,所以我往往会忘记这一点。;-)还要感谢C ++的解释,因为c ++总是令人不寒而栗。
Houman

6
我认为迈克尔提供的解释的一个关键点是实现接口时,必须实现该接口的所有成员,而是从一个抽象类继承时,它不是一个子类必须实现其父成员
吉列尔莫·戈麦斯

82
+1:我愿意打赌,那些主持采访的猴子甚至没有意识到其他语言在OO上的实现方式也不同。
Lightness Races in Orbit

2
@JL我看不出问题出在哪里。您似乎将抽象方法与抽象类混淆了。抽象方法没有实现。但是,在抽象内部,某些方法可以是抽象的(即没有实现),而其他一些方法实际上可以具有实现。
xji 2014年

19
请注意,在Java 8中,您现在可以在接口中具有默认方法和静态方法,这意味着Java接口可以具有实现。参考这里。显然,您主要是指.NET,所以这只是指Java。
davtom

866

打个比方:当我在空军时,我参加了飞行员培训,并成为了美国空军的飞行员。那时我没有资格飞行任何东西,不得不参加飞机型号培训。合格后,我将成为飞行员(抽象班)和C-141飞行员(混凝土班)。在我的一项任务中,我还承担了另一项职责:安全员。现在,我仍然是飞行员和C-141飞行员,但是我也履行了安全主任的职责(可以说我实施了ISafetyOfficer)。不需要飞行员担任安全员,其他人也可以做到。

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

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

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


87
我真的很喜欢这个比喻,它使用一个简单的例子来解释一个稍微复杂的话题
Kevin Bowersox 2012年

13
这是了解复杂的OO术语的最佳方法。简而言之,只有在您可以实际使用它时,所有理论才有价值。@Jay you rexample确实很容易掌握,然后有几个要点(主要是穿透性的思想而不是全神贯注!)
vs

54
我还是有点困惑。假设您现在拥有F-16和T-38资格,所以现在班级Jay不能从多个班级(C-141飞行员,F-16飞行员和T-38飞行员)继承,这是否意味着谁的班级应该成为接口?谢谢
Alex Okrushko 2013年

37
许多人都正确地给Alex的评论+1,因为它显示了此示例中的某些缺点。首先,我想说周杰伦将是C-141Pilot的一个实例,而不是它自己的类。另外,由于在美国空军中,有99%的飞行员一次只能在一架飞机上合格(FCF和测试飞行员是个例外),所以我没有考虑多重资格以及如何实施。据我所知,一名50年前同时拥有25架不同飞机资格的飞行员,我认为这是我们不想使用多重继承的例证。
杰伊,

20
由于一个飞行员不可能一次飞行一架以上的飞机,因此这将是实施该战略模式的好机会。飞行员将拥有一组证书,并在运行时选择正确的证书。认证将被编码为使用TakeOff,Land和Eject方法实现IFlyPlane接口的行为。
迈克尔·布莱克本

221

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

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

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

例如。汽车和卡车具有许多汽车抽象类的核心属性和行为,但是它们还共享一些外围行为,例如“生成排气”,甚至是非汽车类(例如Drillers或PowerGenerators)也具有这种特性,并不一定定义汽车或卡车,因此Car,Truck,Driller和PowerGenerator可以共享同一接口IExhaust。


32
我认为一个更好的类比是“ usesFuel”,它可以显示接口的契约性质。
Pureferret 2012年

@Pureferret如果accelerate是Automobile抽象类的核心行为的一部分,那么我不能说accelerate显示合同性质。合同性质是什么?为什么contract我们每次谈论时都会引入这个词interface
汇率过高的

@overexchange,因为通常接口只是两个“表面”相遇的地方,但是“合同”一词意味着两个“表面” 如何相遇的共识。(至少对我而言)产生废气是您“同意”的事情,这没有任何意义。但是(再次对我而言),您可以同意需要使用燃料是有道理的。
Pureferret 2014年

1
@Pureferret我在链接处提出了同样的问题
过度兑换

1
@Pureferret如果interface需要外围行为,那么为什么public interface List<E> extends Collection<E> {}要设计来描述其核心行为list?这实际上与prasun的答案相矛盾。这两个Collection<E>List<E>这里的接口。
2014年

198

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

接口用于2个相似/不相似类之间的通信,这些类不关心实现接口的类的类型(例如,高度可以是接口属性,可以由Human,Building,Tree来实现。是否可以食用,您可以游泳,可以死去或其他任何事情。.只重要于您需要具有Height的东西(在您的课程中实施))。


7
我真的很喜欢这个答案,因为有时很难通过查看更抽象的东西(例如intent)而不是结构(因为结构上,接口和纯抽象类几乎是相同的)来回答事物之间的“区别” 事情)。
LostSalad 2014年

枚举抽象类与接口在特定语言中可以做什么很容易,但是创建一个抽象以赋予对象以含义和责任则更加困难,而您所说的完全恢复了OO中2概念的使用。谢谢!
塞缪尔

2
@dhananjay:我看到身高可以与动物类的概念区分开,也可以与另一个不同的类分开,但是,类之间的“交流”到底是什么意思?只是为自己的班级定义了高度,对吗?
TTT

77

还有其他一些区别-

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

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

可以在v2 +中修改抽象基类,而无需破坏API。对接口的更改正在破坏更改。

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


5
对于一个类可以实现多个接口的关键点,请+1。
cgp

这是接口相对于抽象基类IMO的真正优势。否则,我同意.NET设计准则,即现在说“
优先

虽然,如果您可以补充一点,那就是它也很接口,它可以应用于任何类。
cgp

1
@altCognito:认为第二段对此有所处理。不过,这确实使我想起了接口适用于值类型,因此我添加了它。
Reed Copsey

非常感谢您的确切描述。确实非常有帮助。我是新来的。可惜您不能选择两个答案作为“答案”。让我困惑的一件事是您对Abstract'base'类的使用。所有抽象类均应作为子类的基类。为什么还要命名“基础”呢?
Houman

68

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

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类。
这里是抽象类和方法的作用。您可以将drive方法定义为抽象,以告诉每个继承的子代必须实现此功能。
因此,如果您修改车辆类别...

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

自行车和汽车还必须指定如何驾驶。否则,代码将无法编译,并引发错误。
简而言之,抽象类是具有某些不完整功能的部分不完整类,继承子级必须指定它们自己的功能。

接口 接口是完全不完整的。它们没有任何属性。它们只是表明继承的孩子有能力做某事。...
假设您随身携带不同类型的手机。它们每个都有不同的方式来执行不同的功能。例如:打电话给一个人。手机的制造商会指定如何进行。手机可以在此处拨打号码-即可以拨打。让我们将其表示为接口。

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

在这里,“可拨号”的制造商定义了如何拨打号码。您只需要给它拨一个号码即可。

// 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接口,而这只是该函数所关心的。

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

更多信息 抽象类与接口


“接口没有任何属性”是不正确的。

@ Bigeyes,Java不允许接口中的属性。我认为其他语言也是如此。你能解释更多吗?
fz_salam

我指的是C#/。Net。请参见示例
Bigeyes,2017年

@Bigeyes for C#,其中的接口可以具有属性,这是否会引入多重继承问题?当一个类使用定义了相同属性的多个接口时会发生什么?只是好奇的感谢
stackPusher

@happycoder:re:“在这里,通过使用接口而不是抽象类,您不必担心它的属性。例如:它是否具有触摸屏或拨号盘,是固定的固定电话还是移动电话。您只需要知道它是否可拨号;它是否继承(或实现)了Dialable接口。” -您可以在代码示例中显示它,也看不到它将如何被继承吗?
TTT

45

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

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

如果满足以下条件,请考虑使用抽象类

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

如果满足以下条件,请考虑使用接口

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

简单来说,我想使用

接口:由多个不相关的对象实施合同

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

看一下代码示例,以清晰的方式理解事物:如何解释接口和抽象类之间的区别?


33

面试官在吠叫。对于C#和Java之类的语言,有区别,但是在诸如C ++之类的其他语言中则没有区别。面向对象理论并没有区别这两者,只是语言的语法。

抽象类是同时具有实现和接口(纯虚拟方法)的类。接口通常没有任何实现,而只有纯虚函数。

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


一周前有人问过我同样的问题,我没有Java经验,但是我已经使用C ++已有一段时间了。采访者在提出问题之前没有指定语言,因此我只是解释说,在这种情况下,接口是没有状态或任何实现的抽象类。我同意这也是一个奇怪的问题。
dacabdi

31

通过实现接口,您将实现组合(“具有”关系)而不是继承(“具有”关系)。这是记住诸如设计模式之类的重要原则,在这种情况下,您需要使用接口来实现行为的组成而不是继承。


17
在IMO中,接口实现了更多的“ Acts-as-a”关系。封装比接口更好地实现了组合。
Reed Copsey

12
我不认为实现接口会受到影响。
Pavan Dittakavi

另外,接口更可能用于描述“功能”,例如IDisposable。它曾经在类之间共享这些类“能够做”的功能。IFlyable的更多示例可以通过鸟和飞机来实现。但是Bird可能来自Class Creature,而craft则来自AirCraft。
Peter.Wang

26

我将解释接口和抽象类的深度详细信息。因此,请检查下面有关Interface和Abstract类的说明。

  1. 什么时候应该使用接口?

    如果您不了解实现,只是我们有需求规范,那么我们就选择接口

  2. 什么时候应该使用Abstract Class?

    如果您知道实现但不完全了解(部分实现),那么我们将使用Abstract类。

    接口

    默认情况下,每种方法的公共抽象表示接口都是100%纯抽象。

    抽象

    可以有Concrete方法和Abstract方法,什么是Concrete方法,在Abstract类中具有实现,Abstract类是声明为abstract的类-它可以包含也可以不包含abstract方法。

    接口

    我们不能将接口声明为私有的,受保护的

    问:为什么我们不声明Interface为私有和受保护?

    因为默认情况下接口方法是公共抽象的,所以我们不将接口声明为私有和受保护的。

    接口方式
    也不能将接口声明为私有,受保护,最终,静态,同步,本机.....

    我将给出原因:为什么我们不能声明同步方法,因为我们无法创建接口的对象并且无法进行同步,所以在对象上也是如此,而我们之所以没有声明同步方法的原因是瞬态概念也不适用,因为带有同步的瞬态工作。

    抽象

    我们很高兴与public,private final static ....一起使用,意味着抽象上没有任何限制。

    接口

    默认情况下,变量在Interface中声明为public static final,因此我们也不将变量声明为受保护的私有变量。

    易失修饰符也不适用于接口,因为默认情况下接口变量是public static final和final变量,一旦将值分配给变量,并且一旦将变量声明为接口,就必须更改变量,就无法更改该值。

    而且volatile变量会不断变化,因此它是opp。最后,这就是为什么我们不在接口中使用volatile变量的原因。

    抽象

    抽象变量无需声明public static final。

我希望这篇文章有用。


4
我不同意这一点:Abstract class must have at lease one abstract method.只要您实现它,就有可能没有Abstract方法的Abstract类。参考: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
Devner 2014年

您在谈论技术细节和实施,没有就一般的OOP回答问题
Billal Begueradj

26

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

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

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

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

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

我相信,两位面试官都期望这两者之间存在直截了当的区别,当您失败时,他们试图通过将ONE实施为OTHER来驱使您走向这种区别。

如果您有一个仅包含抽象方法的Abstract类怎么办?


这就很好地总结了这个问题的答案。
pranavn

实现的功能与扩展的结构,不错!
harshvchawla

21

对于.Net,

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

编辑:在另一个说明上,我什至不使用短语“子类”(或“继承”短语)来描述“定义为实现”接口的类。对我而言,接口是对合同的定义,如果已经定义了一个类以实现该接口,则该类必须遵循该合同。它不会继承任何东西。您必须自己显式添加所有内容。


2
是! 州!那就是第二位面试官用怪异的方式在界面内说“公共变量”的意思。天哪!抽象类可以有状态,接口不能有状态!是的,其他所有人也都同意他们的继承方式之间的差异,我忘记提及了,但是后来已经弄清楚了。:) 谢谢大家!
Houman

4
不仅仅是状态...。抽象类可以具有IMPLEMENTATION。也就是说,他们可以在他们的代码的方法实际运行和做一些事情,这得到由基类的实例......不是这样的接口inhertited和执行
查尔斯BRETANA

从某种意义上讲,甚至还可以实例化Abstract类,而只需要使用派生类定义而不是直接实例化即可。但是在抽象类中定义的状态变量在通过对派生类的实例进行新创建而创建的对象中实例化。这个实例既是抽象类的实例,又是派生类的实例-毕竟是从它派生的。对于接口,这都不是正确的。
查尔斯·布雷塔纳'17

当您新建一个定义为实现接口的类的实例时,它不是该接口的“实例”,所有语法都会使编译器检查该类的代码并确保每种行为(方法,属性,事件,eventHandler等),该接口定义的内容已在该类的代码中实现。
查尔斯·布雷塔纳'17

20

接口:如果要对可能相互关联或不相互关联的组件暗含规则,则应使用

优点:

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

缺点:

  1. 必须执行所有已定义的合同
  2. 不能有变量或委托
  3. 一旦定义,就无法更改而不破坏所有类

抽象类:应在您希望彼此相关的组件具有某些基本或默认行为或实现的地方使用

优点:

  1. 比介面快
  2. 具有实施灵活性(您可以完全或部分实施)
  3. 可以轻松更改而不会破坏派生类

缺点:

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

定义更快。重要吗?这到底是什么意思?在抽象类上进行函数调用的操作码比在接口上进行函数调用的操作码快吗?
denis631

@ denis631抽象类比接口快一点,因为interface方法中涉及搜索和调用。阅读此coderanch.com/t/503450/java/abstract-class-faster-interface
bourax网站管理员

17

我认为他们不喜欢您的回答,因为您提供了技术差异而不是设计差异。这个问题对我来说就像一个巨魔问题。实际上,接口和抽象类具有完全不同的性质,因此您无法真正进行比较。我将为您介绍接口的作用以及抽象类的作用。

接口:用于确保契约并在类之间建立低耦合,以具有更可维护,可伸缩和可测试的应用程序。

抽象类:仅用于分解具有相同责任的类之间的某些代码。请注意,这是为什么在OOP中多重继承是一件坏事的主要原因,因为一个类不应该处理很多职责(使用合成)改为)。

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


13
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。如果抽象类中的所有变量都是公共的,该问题可能会被框起来?好吧,它们仍然可以是非静态的和非最终的,与接口中的变量不同。

最后,我还要为上述提到的内容再加上一点-抽象类仍然是类,并且属于单个继承树,而接口可以存在于多个继承中。


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

这是我的意见。


12

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一起正常工作的原因。通过为基本类型提供良好的默认实现,您可以开始使用有效且经过良好测试的类型。然后,您可以修改需要修改的零件。

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


1
@AbdullahShoaib是-一个人可以做但不能做的事,这里有所不同。这是基本原因,我们需要接口。做事也会成为其中的一部分abstract class
汇率过高的

10

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

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

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


1
使用术语“ 水平”和“ 垂直”可以很清楚地想象出差异。
无限

10

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

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

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

基本上,任何类(无论是否抽象)都与它实质有关,而接口则与您如何使用它有关。例如:Animal可能是一个抽象类,它实现一些基本的代谢功能,并在没有给出实现的情况下指定了呼吸和运动的抽象方法,因为它不知道它应该通过g还是肺呼吸,也不知道它是飞行,游泳,散步还是爬行。Mount另一方面,可能是一个界面,它指定您可以骑乘动物而无需知道它是哪种动物(或根本不是动物!)。

在后台,接口基本上是仅具有抽象方法的抽象类,这一事实无关紧要。从概念上讲,它们扮演着完全不同的角色。


10

由于您可能已经从专家那里获得了理论知识,因此,在此重复这些步骤时,我不会花很多时间,而是让我举一个简单的示例来说明我们可以使用/不能使用Interfaceand Abstract 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";
    }
}

考虑一下,我们为每个汽车有一个单独的类。

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借助其自己的制动技术实现多重继承。


4
由于示例,这是最好的解释之一。
亚当·门多萨

2
这对我来说是有意义的,而无需费劲。我只是想为学生们举一个汽车的例子。感谢您抽出宝贵的时间来整理这些内容。
tazboy

9

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


8

这些答案都太长了。

  • 接口用于定义行为。

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

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


7

1)接口可以看作是一个纯抽象类,它是相同的,但是尽管如此,实现一个接口并从抽象类继承还是不一样。从这个纯抽象类继承时,您将定义一个层次结构->继承,如果您实现的不是接口,则可以实现任意数量的接口,但是只能从一个类继承。

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

例如:

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

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


7

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

可以使用任何依赖关系注入工具来注入接口,其中很少有人支持Abstract类注入。


1
我相信您的意思是DI工具可以注入实现接口的类。一些这样的工具还可以注入派生自抽象类的类,或者您说那是不可能的?
约翰·桑德斯

6

从我的另一个答案中,主要涉及何时使用一个与另一个:

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


6

接口类型与抽象基类

改编自Pro C#5.0和.NET 4.5框架手册。

接口类型似乎与抽象基类非常相似。回想一下,当一个类标记为抽象时,它可以定义任意数量的抽象成员以为所有派生类型提供多态接口。但是,即使类确实定义了一组抽象成员,也可以自由定义任意数量的构造函数,字段数据,非抽象成员(带有实现),等等。另一方面,接口仅包含抽象成员定义。由抽象父类建立的多态接口受到一个主要限制,因为只有派生类型支持抽象父类定义的成员。但是,在较大的软件系统中,开发除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编程语言编写)来实现该接口。如您所见,接口是高度多态的。考虑在系统名称空间中定义的名为.ICloneable的标准.NET接口。该接口定义了一个名为Clone()的方法:

public interface ICloneable
{
object Clone();
}

6

回答第二个问题:默认情况下,publicin interface中定义static finalpublic变量是abstract类中的实例变量。


6

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

如果您通读一些词典和其他字体,可能会发现同一术语具有不同的含义,但具有一些共同的定义。我认为我在本站点中发现的这两种含义非常非常好并且很合适。

接口:

一种事物或情况,可以使单独的,有时是不兼容的元素有效地进行协调。

抽象:

某种东西本身集中于任何更广泛或更普遍的东西或几件事的基本素质;本质。

例:

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

在此处输入图片说明

您的汽车模型为XYZ,属于类型ABC,因此它是混凝土汽车,是汽车的特定实例。汽车不是真正的物体。实际上,它是创建特定对象的一组抽象标准(质量)。简而言之,Car是一个抽象类,它是“某种东西本身就集中了任何更广泛或更普遍的东西的本质素质”

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

在面向对象的观点中,ABC不应将用于体裁的燃料声明为类别,因为那里没有用于特定类型汽车的混凝土燃料。尽管您的汽车可以接受抽象类的Fuel(燃料)或VehicularFuel(车辆燃料),但您必须记住,您现有的仅有部分车辆燃料符合规范,即满足您汽车手册中要求的规范。简而言之,他们应该实现接口 ABCGenreFuel,该接口“ ...使单独的,有时是不兼容的元素能够有效地进行协调”

附录

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

类:

由于共同的属性,特征,品质或特质而被视为一个团体的许多人或事物;类;

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


1
太多的绒毛,不要让听起来比现在更加混乱。
ganjeii '16

5

从编码的角度

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

从设计角度

如果是“是”关系并且您需要部分或全部功能,请将其保留为抽象类。如果是“应做”关系,请将其保留为接口。

确定所需的内容:仅执行策略,或代码可重用性和策略。


3

其他一些差异:

抽象类可以具有静态方法,属性,字段等,而运算符,接口则不能。强制转换运算符允许从抽象类强制转换,但不允许从接口强制转换。

因此,即使从未实现抽象类(通过其静态成员),也可以单独使用抽象类,并且您不能以任何方式单独使用接口。


在Java中,接口可以具有成员变量,但是默认情况下它们可以成为公共静态..so接口可以具有静态字段
Jitendra Vispute

是的,接口可以具有静态字段。BUT接口不能具有静态方法。
学习者
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.