这是因为:
1. 接口存在是为了实现多重继承;
2. 有接口是因为对象的CAN-DO特性应该放在接口而不是基本抽象类中。
请澄清。
抽象类可以指定某些实现,但通常不包括全部实现。(话虽如此,提供没有抽象成员但有大量虚函数和“no-op”实现的抽象类是完全可能的)。接口提供没有实现,只是一个契约。
你当然可以争辩说,如果允许多重继承类,那么接口将几乎没有意义。
就个人而言,我并不关注继承中“is-a”与“can-do”的区别。这从来没有给我很好的直觉提示,帮助我做出决策,最好的方式总是尝试各种想法,看哪个感觉最灵活。(话说回来,我非常支持组合优先于继承...)
编辑:正如反驳lbushkin评论中的第三点最方便的方法...你可以通过密封来覆盖抽象方法以及非虚函数(不能进一步覆盖它):
public abstract class AbstractBase
{
public abstract void Foo();
}
public class Derived : AbstractBase
{
public sealed override void Foo() {}
}
继承自Derived
的类不能再进一步覆盖Foo
。
我并不是在任何方面都建议我们要多重继承实现 - 但是如果我们确实这样做了(以及它带来的复杂性),那么一个仅包含抽象方法的抽象类几乎可以实现与接口相同的功能。(虽然还有显式接口实现,但这是我目前能想到的全部。)
这不是一个琐碎的问题,而是一个非常好的问题,我在面试任何候选人时都会问到。
简而言之,抽象基类定义了一个类型层次结构,而接口定义了一个契约。
你可以将其视为“是一个”与“实现一个”的区别。例如,Account
可以是一个抽象基类,因为你可以拥有 CheckingAccount
,SavingsAccount
等所有派生自抽象基类 Account
的类。抽象基类还可以包含像任何正常类一样的非抽象方法、属性和字段。但是接口只包含必须被实现的抽象方法和属性。
C# 只允许从一个基类派生 - 单继承就像 Java 一样。但是你可以实现任意数量的接口 - 这是因为接口只是一个契约,你的类承诺实现。
所以,如果我有一个类 SourceFile
,那么我的类可以选择实现 ISourceControl
,它说:“我忠实地承诺实现ISourceControl
所需的方法和属性。”
这是一个大题目,可能值得写一篇更好的文章,但我时间不多,希望这可以帮到你!
两种机制都存在的一个重要原因是因为 C# 中只允许单一继承,不像 C++ 一样可以多重继承。类继承只允许你从一个地方继承实现,而其他的必须通过实现接口来完成。
例如,假设我创建了一个类,比如 Car,并将其子类化为三个子类:RearWheelDrive、FrontWheelDrive 和 AllWheelDrive。现在我决定需要沿不同的“轴”划分我的类,比如那些带有按钮启动器和那些没有的类。我希望所有带有按钮启动器的汽车都有一个“PushStartButton()”方法,而非按钮启动器的汽车都有一个“TurnKey()”方法,并且我想能够处理与它们的启动相关的 Car 对象,而不考虑它们属于哪个子类。我可以定义接口,让我的类可以实现,例如 IPushButtonStart 和 IKeyedIgnition,这样我就有了一种通用的方式来处理这些对象的区别,而这种区别与每个对象所继承的单一基类无关。
你已经给出了一个很好的答案。我认为你的第二个答案才是真正的原因。如果我想让一个对象具有可比性,我不应该从可比较的基类派生。如果你考虑所有的接口,想象一下你需要处理像 IComparable 这样的基本接口的所有排列组合。
接口让我们定义一个对象提供的公开行为的契约。抽象类允许您定义行为和实现,这是非常不同的事情。
接口存在的目的是为了提供一个没有任何实现的类,以便.NET可以在托管环境中支持安全和功能性的多重继承。
这个想法很简单 - 如果你的类(YourClass)已经继承了一个父类(SomeParentClass),同时你想让你的类(YourClass)拥有一些在某个抽象类(SomeAbstractClass)中定义的新行为,你不能简单地从那个抽象类(SomeAbstractClass)继承,因为C#不允许多重继承。 然而,如果你的新行为是在一个接口(IYourInterface)中定义的,你可以轻松地从这个接口(IYourInterface)以及父类(SomeParentClass)中继承。
考虑一个类Fruit,它被两个子类(Apple和Banana)继承,如下所示:
class Fruit
{
public virtual string GetColor()
{
return string.Empty;
}
}
class Apple : Fruit
{
public override string GetColor()
{
return "Red";
}
}
class Banana : Fruit
{
public override string GetColor()
{
return "Yellow";
}
}
public interface ICloneable
{
object Clone();
}
现在,如果我想让我的Apple类(而不是Banana类)可克隆,我可以简单地实现ICloneable,如下所示:
class Apple : Fruit , ICloneable
{
public object Clone()
{
// add your code here
}
public override string GetColor()
{
return "Red";
}
}
现在考虑你对于纯抽象类的论点,如果 C# 有一个纯抽象类,比如说 Clonable,而不是像这样使用接口 IClonable:
abstract class Clonable
{
public abstract object Clone();
}
你能否通过继承抽象类 Clonable 而不是 IClonable,使你的 Apple 类可克隆?像这样:
// Error: Class 'Apple' cannot have multiple base classes: 'Fruit' & 'Clonable'
class Apple : Fruit, Clonable
{
public object Clone()
{
// add your code here
}
public override string GetColor()
{
return "Red";
}
}
接口定义了一个实现类必须满足的契约;它是一种声明“这个做那个”的方式。抽象类是一个类的部分实现,根据定义是不完整的,需要派生才能完成。它们是非常不同的东西。
它们具有两个明显不同的目的。
抽象类提供了一种从定义的合同中继承对象的方式,并允许在基类中指定行为。从理论上讲,这提供了一个IS-A关系,即具体类是基类的特定类型。
接口允许类定义它们将履行的一个(或多个)合同。它们允许ACTS-AS或“可以用作”类型的关系,而不是直接继承。这就是为什么通常接口会使用形容词作为名称(IDisposable),而不是名词。