25

我知道抽象类是一种无法实例化的特殊类。抽象类只能被子类化(继承自)。换句话说,它只允许其他类继承它,但不能实例化。优点是它可以为所有子类强制执行某些层次结构。简单来说,它是一种强制所有子类继承相同层次结构或标准的契约。

我也知道接口不是一个类。它是由“接口”一词定义的实体。接口没有实现;它只有签名,或者换句话说,只有没有主体的方法的定义。作为与抽象类的相似之处之一,它是一种契约,用于定义所有子类的层次结构,或者它定义了一组特定的方法及其参数。它们之间的主要区别在于,一个类可以实现多个接口,但只能从一个抽象类继承。由于 C# 不支持多重继承,因此使用接口来实现多重继承。

当我们创建一个接口时,我们基本上是在创建一组没有任何实现的方法,这些实现必须被实现的类覆盖。优点是它为一个类提供了一种成为两个类的一部分的方法:一个来自继承层次结构,一个来自接口。

当我们创建一个抽象类时,我们正在创建一个基类,它可能具有一个或多个已完成的方法,但至少有一个或多个方法未完成并声明为抽象类。如果抽象类的所有方法都未完成,那么它与接口相同。

但是 但是 但是

我注意到我们将在 C# 8.0 中使用默认接口方法

也许我问它是因为我只有 1-2 年的编程经验,但是现在抽象类和接口之间的主要区别是什么?

我知道我们不能在界面中制作状态,它们之间只有一个区别吗?

4

6 回答 6

9

除了抽象类可以具有状态而接口不能具有明显的事实之外,两者之间没有太大区别。默认方法或也称为虚拟扩展方法实际上已经在 J​​ava 中可用了一段时间。默认方法的主要驱动力是接口演变,这意味着能够在未来版本中向接口添加方法,而不会破坏与该接口现有实现的源代码或二进制兼容性。

这篇文章提到的另外几个优点:

于 2017-12-01T21:30:08.740 回答
8

概念的

首先,类和接口之间存在概念上的区别。

  • 一个类应该描述一个“是一个”关系。例如法拉利是一辆汽车
  • 一个接口应该描述一个类型的契约。例如,汽车有方向盘。

目前抽象类有时用于代码重用,即使没有“是”关系。这污染了OO设计。例如FerrariClass继承自CarWithSteeringWheel

好处

  • 因此,从上面看,您可以重用代码而不引入(概念上错误的)抽象类。
  • 您可以从多个接口继承,而抽象类只是单继承
  • 接口存在协变和逆变,C# 中的类没有
  • 实现接口更容易,因为某些方法具有默认实现。这可以为接口的实现者节省大量工作,但用户不会看到区别:)
  • 但对我来说最重要的是(因为我是库维护者),您可以在不进行重大更改的情况下向接口添加新方法!在 C# 8 之前,如果一个接口被公开发布,它应该被修复。因为更改界面可能会破坏很多。

记录器界面

这个例子显示了一些好处。

您可以描述一个(过于简单的)记录器接口,如下所示:

interface ILogger
{
    void LogWarning(string message);

    void LogError(string message);

    void Log(LogLevel level, string message);
}

然后,该界面的用户可以使用LogWarning和轻松记录为警告和错误LogError。但缺点是实现者必须实现所有方法。

一个更好的默认界面是:

interface ILogger
{
    void LogWarning(string message) => Log(LogLevel.Warning, message);

    void LogError(string message) => Log(LogLevel.Error, message);

    void Log(LogLevel level, string message);
}

现在用户仍然可以使用所有方法,但实现者只需要实现Log. 此外,他可以实现LogWarningand LogError

此外,将来您可能希望添加 logLevel “Catastrophic”。在 C#8 之前,您无法在LogCatastrophic不破坏所有当前实现的情况下将该方法添加到 ILogger。

于 2019-09-26T12:35:04.197 回答
3

仍然使界面独特的另一件事是协变/逆变

老实说,从来没有发现自己处于默认 impl 的情况。在界面中是解决方案。我对此有点怀疑。

于 2017-12-01T21:33:01.350 回答
3

抽象类和新的默认接口方法都有其适当的用途。

A. 原因

没有引入默认接口方法来替代抽象类。

C# 8.0 中的新增功能说明:

此语言功能使 API 作者能够在以后的版本中向接口添加方法,而不会破坏与该接口的现有实现的源代码或二进制兼容性。现有实现继承默认实现。

此功能还使 C# 能够与支持类似功能的面向 Android 或 Swift 的 API 互操作。默认接口方法还支持类似于“特征”语言功能的场景。

B. 功能差异

抽象类和接口之间仍然存在显着差异(即使使用默认方法)。

以下是接口仍然不能拥有/做的一些事情,而抽象类可以

  • 有一个构造函数,
  • 保持状态,
  • 从非抽象类继承,
  • 有私有方法。

C. 设计

虽然默认接口方法使接口更加强大,但抽象/基类和接口仍然代表着根本不同的关系。

(从在设计 C# 类库时我应该从何时选择继承而不是接口?

  • 继承描述的是一种关系。
  • 实现一个接口描述了一种可以做的关系。
于 2019-10-29T11:20:08.227 回答
1

我想到的唯一主要区别是您仍然可以重载接口永远不会拥有的抽象类的默认构造函数。

abstract class LivingEntity
{
    public int Health
    {
        get;
        protected set;
    }


    protected LivingEntity(int health)
    {
        this.Health = health;
    }
}

class Person : LivingEntity
{
    public Person() : base(100)
    { }
}

class Dog : LivingEntity
{
    public Dog() : base(50)
    { }
}
于 2018-01-23T10:45:33.200 回答
0

两个主要区别:

  • 抽象类可以有状态,但接口不能。
  • 一个类型可以派生自一个抽象类,但可以实现多个接口。

当涉及到默认修饰符时,还有一些其他较小的差异。

于 2019-09-27T23:04:34.370 回答