默认接口方法。 abstract class 和 interface 之间有什么深刻的区别?

Default Interface Methods. What is deep meaningful difference now, between abstract class and interface?

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

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

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

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

但是 但是 但是

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

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

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

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

这个 post 提到的另外两个优点:

另一个使界面仍然独一无二的是covariance / contravariance

老实说,我从来没有遇到过默认实现的情况。在界面中是解决方案。我对此有点怀疑。

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

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)
    { }
}

概念性的

首先,class 和接口之间存在概念上的差异。

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

当前抽象 classes 有时用于代码重用,即使没有 "is a" 关系。这会污染 OO 设计。例如。 FerrariClass 继承自 CarWithSteeringWheel

好处

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

记录器界面

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

您可以描述一个(过于简化的)记录器界面如下:

interface ILogger
{
    void LogWarning(string message);

    void LogError(string message);

    void Log(LogLevel level, string message);
}

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

更好的默认界面是:

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。此外,他 可以 实现 LogWarningLogError

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

两个主要区别:

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

默认修饰符还有其他一些较小的差异。

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

一个。原因

尚未引入默认接口方法来替代抽象 classes。

What's new in C# 8.0 状态:

This language feature enables API authors to add methods to an interface in later versions without breaking source or binary compatibility with existing implementations of that interface. Existing implementations inherit the default implementation.

This feature also enables C# to interoperate with APIs that target Android or Swift, which support similar features. Default interface methods also enable scenarios similar to a "traits" language feature.

乙。功能差异

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

以下是接口仍然不能have/do而抽象class可以的一些事情:

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

摄氏度。设计

虽然默认接口方法使接口更加强大,abstract/base classes 和接口仍然代表根本不同的关系。

(From When should I choose inheritance over an interface when designing C# class libraries?)

  • Inheritance describes an is-a relationship.
  • Implementing an interface describes a can-do relationship.