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

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

接口:

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

抽象类:

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

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

另请参阅:

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


当前回答

1) 接口可以被视为纯抽象类,这是相同的,但尽管如此,实现接口和从抽象类继承并不相同。当你从这个纯抽象类继承时,你定义了一个层次结构->继承,如果你实现了你不需要的接口,你可以实现任意多的接口,但是你只能从一个类继承。

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

例如:

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

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

其他回答

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

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

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

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

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

由于您可能已经从专家那里获得了理论知识,所以我不会花太多的时间来重复这里的所有内容,而是让我用一个简单的例子来解释,我们可以使用/不能使用Interface和Abstract类。

假设您正在设计一个应用程序来列出汽车的所有功能。在不同的方面,您需要共同继承,因为一些财产,如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的帮助下通过自己的制动技术实现多重继承。

通过实现接口,您实现了组合(“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;
}

还有一些其他的区别-

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

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

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

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