我最近接受了两次电话采访,被问及接口类和抽象类之间的区别。我已经解释了我能想到的每一个方面,但似乎他们在等我提一些具体的事情,我不知道是什么。
根据我的经验,我认为以下是正确的。如果我遗漏了一个要点,请告诉我。
接口:
接口中声明的每个方法都必须在子类中实现。接口中只能存在事件、委托、财产(C#)和方法。一个类可以实现多个接口。
抽象类:
子类只能实现抽象方法。抽象类可以具有具有实现的普通方法。除了事件、委托、财产和方法之外,抽象类还可以有类变量。由于C#中不存在多重继承,一个类只能实现一个抽象类。
在这之后,面试官提出了一个问题:“如果你有一个只有抽象方法的抽象类呢?这和接口有什么不同?”我不知道答案,但我认为这是上面提到的继承,对吧?另一位面试官问我,“如果你在接口中有一个公共变量,那会和抽象类中有什么不同?”我坚持认为你不能在接口中使用公共变量。我不知道他想听什么,但他也不满意。
另请参阅:
何时使用接口而不是抽象类,反之亦然接口与抽象类如何决定使用抽象类和接口?接口和抽象类之间的区别是什么?
接口:如果您希望在组件上暗示规则,则应使用该接口彼此相关的
赞成的意见:
允许多重继承通过不公开上下文中使用的确切类型的对象来提供抽象通过合同的特定签名提供一致性
欺骗:
必须执行所有定义的合同不能有变量或委托一旦定义,就不能在不破坏所有类的情况下进行更改
抽象类:应用于希望对彼此相关的组件具有一些基本或默认行为或实现的情况
赞成的意见:
比接口更快在实施中具有灵活性(您可以完全或部分实施)可以在不破坏派生类的情况下轻松更改
欺骗:
无法实例化不支持多重继承
抽象类处理有效地打包类功能,而接口用于意图/契约/通信,并且应该与其他类/模块共享。
使用抽象类作为契约和(部分)契约实现者违反了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;
}
Jeffrey Richter通过C#从CLR复制。。。
我经常听到这样一个问题,“我应该设计一个基类型还是一个接口?”答案并不总是清晰明了。
以下是一些可能对您有所帮助的指南:
■■ IS-A与CAN-DO关系A类型只能继承一个实现。如果导出类型不能声明与基类型的IS-A关系,不要使用基类型;使用接口。接口意味着CAN-DO关系。如果CAN-DO功能似乎属于对于各种对象类型,使用接口。例如,类型可以转换自身的实例类型可以将其自身的实例序列化(ISerializable),注意,值类型必须从System.ValueType派生,因此不能可以从任意基类派生。在这种情况下,您必须使用CAN-DO关系并定义接口。
■■ 易用性作为开发人员,定义从基类型,而不是实现接口的所有方法。基本类型可以提供许多功能,因此派生类型可能只需要对其行为进行相对较小的修改。如果提供接口,则新类型必须实现所有成员。
■■ 一致的实施无论接口合同记录得多么好每个人都不可能100%正确地履行合同。事实上,COM这就是为什么某些COM对象只能与微软Word或Windows Internet Explorer。通过为基础类型提供默认实现,您首先使用一种有效且经过良好测试的类型;那么你可以修改需要修改的零件。
■■ 版本控制如果向基类型添加方法,则派生类型继承新方法,您开始使用一种有效的类型,用户的源代码甚至不必重新编译。向接口添加新成员将强制接口的继承者更改它的源代码并重新编译。
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?"
文档明确指出,如果抽象类只包含抽象方法声明,则应将其声明为接口。
An another interviewer asked me what if you had a Public variable inside
the interface, how would that be different than in Abstract Class?
默认情况下,接口中的变量是公共静态变量和最终变量。问题的框架可以是,如果抽象类中的所有变量都是公共的呢?与接口中的变量不同,它们仍然可以是非静态和非最终的。
最后,我想对上面提到的内容再补充一点——抽象类仍然是类,属于单个继承树,而接口可以存在于多个继承中。
大多数答案都集中在抽象类和接口之间的技术差异上,但从技术上讲,接口基本上是一种抽象类(没有任何数据或实现),我认为概念上的差异要有趣得多,这可能是面试官想要的。
接口是一种协议。它规定:“这就是我们将要彼此交谈的方式”。它不能有任何实现,因为它不应该有任何实现。这是一份合同。这就像C中的.h头文件。
抽象类是一个不完整的实现。类可以实现接口,也可以不实现接口,抽象类不必完全实现它。没有任何实现的抽象类有点无用,但完全合法。
基本上,任何类,无论抽象与否,都是关于它是什么,而接口则是关于你如何使用它。例如:Animal可能是一个抽象类,它实现了一些基本的代谢功能,并在没有给出实现的情况下指定了呼吸和运动的抽象方法,因为它不知道它应该通过鳃还是肺呼吸,行走或爬行。另一方面,Mount可能是一个接口,它指定你可以骑动物,而不知道它是什么动物(或者它是否是动物!)。
在幕后,接口基本上是一个只有抽象方法的抽象类,这一点无关紧要。从概念上讲,他们扮演着完全不同的角色。