【问题标题】:How Does MixIns (Or Interfaces) solves the diamond problemMixIns(或接口)如何解决钻石问题
【发布时间】:2022-01-07 06:30:13
【问题描述】:

我一直在尝试理解 Mixins,我在 Wikipedia 上发现可以使用 Mixins 来避免钻石问题。 Link

如何通过使用 Mixins(或 java 中的接口)避免 Diamond 问题

【问题讨论】:

  • @FedericoklezCulloca 该消息来源根本没有提到 mixins。维基百科引用它只是为了支持多重继承会导致问题的说法。
  • @kaya3 你说得对。 Brainfart 让我把钻石问题的描述读成……钻石问题的解决方案……

标签: python java oop mixins


【解决方案1】:

在我写这个答案时,那篇维基百科文章当然需要一些爱。

鉴于您在 java 中进行了标记,具体来说:

  • “钻石问题”用词不当;与所有条带的多重继承有关的问题很多,java只是避免了其中的一些;具有任何形式的多重继承的语言都不可能解决所有这些问题。
  • 据推测,至少就 java 而言,diamond 的关键问题在于 java 在这方面与 C++ 的不同之处,因为在最初开发 java 时,大多数语言设计决策都是“我们只是做了 C(++) 所做的事情”,或者“我们决定偏离,因为 [C(++) 在这里所做的普遍接受的问题]”的味道。戴蒙德就是其中之一。具体来说:“与 C++ 不同,java 不允许扩展多个类,因为 [各种问题通常称为菱形问题]”。
  • 这意味着,具体来说,ordering:如果您同时拥有 Parent1Parent2 作为父类型(可能是因为假设的 extends Parent1, Parent2 或者是因为 extends Parent1 implements Parent2 或 @ 987654325@ - 最后两个在 java 中有效,Parent1 和 Parent2 都有相同的方法(例如,void method()),那么哪个实现“获胜”?
  • Java 采用以下格言:顺序永远不重要。换句话说,如果 Parent1 和 Parent2 都提供相同方法的实现,那么在 C++ 中,第一个列出的类“获胜”。在 Java 中,您不能从 2 个类继承,所以这一点没有实际意义。您可以继承多个接口,但作为普通(即没有默认方法)接口不带来实现,这里没有任何问题:是的,如果您实现 Parent1Parent2(本示例中的两个接口),您必须编写void foo() 的实现。如果你同时实现它们,你...必须为void foo() 编写一个实现。没问题。

因此,让我们继续将“钻石问题”定义为“避免订购问题”。此外,因为它是java,你仍然不能扩展不止一件事,所以我们实际上只是在谈论接口的顺序。如果您编写implements Parent1, Parent2,那么如果您编辑该源文件以读取implements Parent2, Parent1,是否有任何变化?在 C++ 中,答案是:“是的,这很重要”。在java中,答案应该是:“不,这是一个毫无意义的改变”。鉴于 java 中现在存在默认方法,让我们看看 java 是如何“解决”这个问题的:

extends Parent1 implements Parent2

给定:

class Parent1 {
  public void foo() {
    System.out.println("Parent1.foo");
  }
}

interface Parent2 {
  default void foo() {
    System.out.println("Parent2.foo");
  }
}

class Foo extends Parent1 implements Parent2 {}

然后这将编译,new Foo().foo(); 将打印Parent1.foo。这是因为在 java 中,类层次结构胜出:接口仅指定一个默认实现,如果没有其他 impl 可用,则使用该实现。还有一个可用的(来自Parent1),因此,它赢了。

您是否认为这是钻石问题的“解决方案”,取决于您如何定义“钻石问题”一词。没有一个统一的定义是普遍认可的。

implements Parent1, Parent2

给定:

interface Parent1 {
  default void foo() {
    System.out.println("Parent1.foo");
  }
}

interface Parent2 {
  default void foo() {
    System.out.println("Parent2.foo");
  }
}

class Foo implements Parent1, Parent2 {}

那么上面的无法编译。 Javac 检测到存在冲突(同一方法的 2 个相互竞争的实现),并且由于 顺序永远不重要,java 不能仅仅因为它被列在首位而给予这两种优先处理。

因此,javac 只会出错。您可以通过为该方法显式编写 impl 来修复它。您甚至可以选择一个(或两个!)父实现:

class Foo implements Parent1, Parent2 {
  @Override public void foo() {
    Parent1.super.foo();
    Parent2.super.foo();
  }
}

现在new Foo().foo() 将打印:

Parent1.foo
Parent2.foo

因此,即使 java 中的默认方法现在已经成为一件事(这正是“混入”的含义,所以即使该维基百科没有将 java 列为具有混入的语言,维基百科页面是错误的,事实上后面关于java的部分也是这么说的!) - 顺序仍然无关紧要。

【讨论】:

  • 感谢您的明确回答@rzwitserloot,我认为 java 有 MixIns。但是你已经清楚地回答了我关于java中钻石问题的问题。现在,如果我可能会问,在其他使用 Mixins 的语言中,我认为相同的概念适用于那些语言(排序或调用超类)
  • 是的,'第一个在排序中获胜'与'如果存在冲突则错误,必须编写显式实现(这可能只是将工作分包给一个继承的 impls)'是仅有的两个我知道的策略。大多数语言都与顺序有关,因为大多数语言不喜欢错误。从原则上讲,它们是错误的(代码:如果解析器不太确定作者的意思,请出错。我宁愿花 5 秒钟来澄清 100 次而不是 30 分钟追查错误)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-19
  • 1970-01-01
  • 2017-07-02
  • 1970-01-01
相关资源
最近更新 更多