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

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

接口:

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

抽象类:

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

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

另请参阅:

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


当前回答

遗产考虑一辆汽车和一辆公共汽车。它们是两种不同的车辆。但是,它们仍然有一些共同的财产,比如它们有方向盘、制动器、齿轮、发动机等。因此,使用继承概念,这可以表示如下。。。

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表单及其子表单,如上文所示。

抽象类

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

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

Bicycle和Car还必须指定如何驾驶它。否则,代码将无法编译并引发错误。简言之抽象类是具有一些不完整函数的部分不完整类,继承的子类必须指定自己的函数。

接口接口完全不完整。他们没有任何财产。他们只是表明继承的孩子有能力做某事。。。假设你随身携带不同类型的手机。他们每个人都有不同的方式来完成不同的功能;打电话给某人。手机制造商指定了如何操作。在这里,手机可以拨打一个号码,也就是说,它是可拨打的。让我们将其表示为一个接口。

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的函数的编写者无需担心其财产。它有触摸屏或拨号盘吗?是固定固定电话还是移动电话。你只需要知道它是否可以拨号;它是否继承(或实现)可拨号接口。

更重要的是,如果有一天你将可拨号电话换成另一个

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

您可以确定代码仍然完美运行,因为使用可拨号接口的功能不(也不可能)依赖于可拨号接口中指定的细节以外的细节。它们都实现了可拨号接口,这是函数唯一关心的事情。

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

更多信息抽象类与接口

其他回答

还有一些其他的区别-

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

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

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

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

其他一些区别:

抽象类可以有静态方法、财产、字段等,而操作符和接口则不能。强制转换运算符允许向抽象类强制转换,但不允许向接口强制转换。

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

虽然您的问题表明它是针对“通用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)

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

这是我的看法。

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

使用抽象类作为契约和(部分)契约实现者违反了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;
}