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

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

接口:

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

抽象类:

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

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

另请参阅:

何时使用接口而不是抽象类,反之亦然接口与抽象类如何决定使用抽象类和接口?接口和抽象类之间的区别是什么?


当前回答

还有一些其他的区别-

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

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

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

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

其他回答

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

如果你阅读一些字典和其他字体,你可能会发现同一个词有不同的含义,但有一些共同的定义。我认为我在这个网站上找到的这两个意思真的非常好,非常适合。

接口:

使分离的、有时不相容的元素能够有效协调的事物或环境。

摘要:

集中于自身的东西,集中于任何更广泛或更普遍的事物或若干事物的本质;本质

例子:

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

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

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

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

补遗

此外,我认为你应该记住class这个词的含义,它是(来自前面提到的同一个网站):

类别:

一群人或事物由于共同的属性、特征、品质或特点而被视为组成一个群体;友善的

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

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

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

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

因此,接口具有真正的体系结构角色,而抽象类几乎只是实现的一个细节(当然,如果正确使用的话)。

抽象类处理有效地打包类功能,而接口用于意图/契约/通信,并且应该与其他类/模块共享。

使用抽象类作为契约和(部分)契约实现者违反了SRP。使用抽象类作为契约(依赖关系)限制了创建多个抽象类以获得更好的重用性。

在下面的示例中,使用抽象类作为OrderManager的合同会产生问题,因为我们有两种不同的处理订单的方式-基于客户类型和类别(客户可以是直接或间接的,也可以是黄金或白银)。因此,接口用于契约,抽象类用于不同的工作流实施

public interface IOrderProcessor
{
    bool Process(string orderNumber);
}

public abstract class CustomerTypeOrderProcessor: IOrderProcessor
{
    public bool Process(string orderNumber) => IsValid(orderNumber) ? ProcessOrder(orderNumber) : false;

    protected abstract bool ProcessOrder(string orderNumber);

    protected abstract bool IsValid(string orderNumber);
}

public class DirectCustomerOrderProcessor : CustomerTypeOrderProcessor
{
    protected override bool IsValid(string orderNumber) => string.IsNullOrEmpty(orderNumber); 

    protected override bool ProcessOrder(string orderNumber) => true; 
}

public class InDirectCustomerOrderProcessor : CustomerTypeOrderProcessor
{
    protected override bool IsValid(string orderNumber) => orderNumber.StartsWith("EX");

    protected override bool ProcessOrder(string orderNumber) => true;
}

public abstract class CustomerCategoryOrderProcessor : IOrderProcessor
{
    public bool Process(string orderNumber) => ProcessOrder(GetDiscountPercentile(orderNumber), orderNumber);

    protected abstract int GetDiscountPercentile(string orderNumber);

    protected abstract bool ProcessOrder(int discount, string orderNumber);
}

public class GoldCustomer : CustomerCategoryOrderProcessor
{
    protected override int GetDiscountPercentile(string orderNumber) => 15;

    protected override bool ProcessOrder(int discount, string orderNumber) => true;

}

public class SilverCustomer : CustomerCategoryOrderProcessor
{
    protected override int GetDiscountPercentile(string orderNumber) => 10;

    protected override bool ProcessOrder(int discount, string orderNumber) => true;

}

public class OrderManager
{
    private readonly IOrderProcessor _orderProcessor;// Not CustomerTypeOrderProcessor or CustomerCategoryOrderProcessor 

    //Using abstract class here would create problem as we have two different abstract classes
    public OrderManager(IOrderProcessor orderProcessor) => _orderProcessor = orderProcessor;
}

接口类型与抽象基类

改编自Pro C#5.0和.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#不支持类的多重继承。因此,如果您想创建一辆小型货车,它是一辆汽车,并且是一种可克隆类型,那么您无法做到:

// 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();
}

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

在.NET中(类似于Java):

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

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

事实上,有一种叫做非虚拟接口(NVI)的C++习惯用法,其中公共方法是“thunk”到私有虚拟方法的非虚拟方法:

http://www.gotw.ca/publications/mill18.htmhttp://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-虚拟界面(_I)