默认方法接口 vs 抽象类,它们的动机是什么?

15

上下文

我最近看到了这个C#提案默认接口方法,我已经阅读了规范和更重要的是动机。可能我错过了什么,但是动机让我有点不舒服。

接口和完全抽象类之间唯一的实际区别曾经是,未来的类可以实现(因此是[IS A])多个接口,但只能继承(因此是[IS A])一个抽象类(以及所有的后果)

对我来说不清楚的是,除了我们可以使用具有默认方法的接口带入多(实现)继承(这在抽象类中不可能),现在接口和默认方法之间的确切差异是什么。 (我不想开放问题/讨论它是否好或坏,这不是这里的主题)

然而,动机谈论完全不同的三个要点:

  • "...API作者可以在将来的版本中添加接口方法而不会破坏源代码..."。嗯,“API”作者也可以在未来版本中向抽象类中添加方法如果他们实现了它们而不会破坏任何东西。
  • "...使C#能够与针对Android(Java)和iOS(Swift)的API进行交互,..."。我认为语言设计决策,特别是关于抽象和OOP模式的多重继承高于与Swift进行交互。我也认为,这只是0.0x%的互操作问题,也可以用其他方式解决。
  • "事实证明,添加默认接口实现提供了“特征”语言特性的元素..."。这是一个非常肤浅的陈述,特别是它涉及到维基百科的“特征”。按照定义,特征允许添加方法而不需要多重继承(与超类之间具有 [IS A] 关系)。然而,接口确实与 [IS A] 有关...更不用说特征至少是可辩论的良好实践了。

问题

我的问题是真正的区别(或动机)是什么,或者我错过了什么?


6
关于第一条,暂时不要考虑抽象类,只考虑接口。你发布了项目的v1版本,很多人已经实现了这个接口,现在你想在接口中添加一个方便的方法。这就是场景,与抽象类无关。你并不认为这个动机特别令人信服,并不意味着一定存在其他动机。 - Damien_The_Unbeliever
2
谢谢,好观点,实际上这值得回答,我认为。 - g.pickardou
1
扩展方法是您可以为接口添加“便捷方法”的另一种方式。由于接口不允许维护状态,添加扩展方法将允许您实现相同的功能。我个人认为这种改变使事情变得更加混乱了。 - Matt M
2个回答

2
他们在Java 8中添加了这个功能。所以你可以添加Java标签并询问Java开发人员它能做什么。显然,它也存在于HaskellScala中。

多继承

我首先想到的是多继承。由于一个类可以实现多个接口,因此你可以使用它来解决菱形继承问题

在Java中,这是他们的做法:

public interface InterfaceA {
    public default void foo() {
        System.out.println("A -> foo()");
    }
}

public interface InterfaceB {
    public default void foo() {
        System.out.println("B -> foo()");
    }
}

private class Test implements InterfaceA, InterfaceB {
    // Compilation error : "class Test inherits unrelated defaults for foo() from types InterfaceA and InterfaceB"
}

所以你需要实现这些方法(覆盖默认实现),或者调用其中一个 super 方法:

public class Test implements InterfaceA, InterfaceB {
     public void foo() {
        InterfaceB.super.foo();
    }
}

虽然 Haskell 类型类与 Java/C# 接口有相似的作用,但它更像是 Scala 的隐式类实现一个共同的 trait。当你调用类型类方法时,实例会根据参数类型进行静态分派。C# 也有类似的提案:https://github.com/dotnet/csharplang/issues/110。 - P. Frolov

2

引入默认方法后,接口和抽象类似乎变得相同了。然而,在Java 8中它们仍然是不同的概念。

抽象类可以定义构造函数。它们更有结构并且可以与状态相关联。而相比之下,默认方法只能在调用其他接口方法的条件下实现,不能引用特定实现的状态。因此,两者用于不同的目的,具体选择取决于场景上下文。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接