使用默认接口方法实现方法 - 矛盾吗?

Cri*_*ike 12 java inheritance interface java-8 default-method

介绍

我已经在SO上阅读了关于实现接口和抽象类的多篇帖子.我特别想找到一个我想链接的地方 - 链接 - 接口与默认方法vs抽象类,它涵盖了同样的问题.作为公认的答案,建议在可能的情况下使用接口的默认方法.但是这个答案下面的评论说"这个功能对我来说更像是黑客"解释了我的问题.

引入了默认方法以使接口的实现更加灵活 - 当接口发生更改时,实现类中不一定需要(重新)编写代码.因此,使用接口的默认方法只是为了实现所有实现类中的方法 - 引用:"感觉更像是对我的黑客攻击".

我考试的例子:

课程概述:

  • Item - 所有项目的抽象超类
  • 水 - 消耗品
  • 石头 - 非消耗品
  • 消耗品 - 与消耗品的某些方法接口(这些方法必须被所有实施类覆盖)

结合这些:

水是物品并实施消耗品; Stone也是一个项目,并没有实现消耗品.

我的考试

我想实现一个所有项目必须实现的方法.因此,我在类Item中声明了签名.

protected abstract boolean isConsumable(); 
//return true if class implements (or rather "is consumable") Consumable and false in case it does not
Run Code Online (Sandbox Code Playgroud)

快速编辑:我知道instanceof可以解决这个特定的例子 - 如果可能的话,想一个更复杂的例子,这使得有必要首先实现该方法.(感谢Sp00m和Eugene)

现在我有几个选择:

  1. 在Item的每个子类中手动实现该方法(在扩展应用程序时绝对不可能).

如上所述,当缩放应用程序时,这将是不切实际或非常低效的.

  1. 在接口内部实现方法作为默认方法,因此Consumable类已经实现了超类Item所需的方法.

这是其他帖子推荐的解决方案 - 我看到以这种方式实现它的优势:

引用 - "关于这个新功能的好处是,在你被迫使用抽象类来实现方便方法之前,从而将实现者限制为单继承,现在你可以只使用接口和一个非常干净的设计最少的实施工作强加给程序员." 链接

但在我看来,我在介绍中提到的默认方法的最初想法似乎仍然是矛盾的.此外,在扩展应用程序并引入更多共享相同实现的方法(作为示例方法isConsumable())时,接口将实现几个默认方法,这与未实现实际方法的接口的想法相矛盾.

  1. 引入子超类而不是接口 - 例如类Consumable作为Item的抽象子类和Water的超类.

它提供了为Item(示例isConsumable() //return false:)中的方法编写默认大小写的机会,然后在子超类中重写它.此处出现的问题:在扩展应用程序并引入更多子超类(作为Consumable类)时,实际的Items将开始扩展多个子超类.这可能不是一件坏事,因为它也必须对接口做同样的事情,但它使继承树变得复杂 - 示例:一个项目现在可能扩展子类别ALayer2,它是ALayer1的子超类,它扩展了Item(layer0) .

  1. 引入另一个超类(因此与Item相同的层) - 例如类Consumable作为抽象类,它将是Water的另一个超类.这意味着Water必须扩展Item&Consumable

此选项提供灵活性.可以为新的超类创建一个全新的继承树,同时仍然能够看到Item的实际继承.但我发现的缺点是在实际类中实现了这个结构,并在以后使用它们 - 示例:我怎么能说:当耗材能够拥有不属于项目的子类时,耗材是一个项目.整个转换过程可能会引起头疼 - 比选项3的结构更可能.

实现这种结构的正确选择是什么?

  • 这是我列出的选项之一吗?
  • 它是那些变种吗?
  • 或者这是我没想过的另一种选择呢?

我选择了一个非常简单的示例 - 请在回答时保持可扩展性,以便将来实现.在此先感谢您的帮助.

编辑#1

Java不允许多重继承.这将影响选项4.使用多个接口(因为您可以实现多个接口)可能是一种解决方法,遗憾的是,default-method将再次成为必需,这正是我最初试图避免的那种实现.链接 - 可能的解决方案的多重继承问题

Rol*_*and 4

我缺少选项 5(或者也许我没有正确阅读):提供其Item自身内部的方法。

假设消耗品可以通过Consumable- 接口识别,这就是我不能推荐您列出的大部分要点的原因:第一个(即在每个子类中实现它)对于像this instanceof Consumable. 第二个可能还可以,但不是我的第一选择。第三个和第四个我根本不推荐。如果我只能给出一个建议,那么可能是要两次考虑继承,并且永远不要仅仅因为中间类在某个时间点让你的生活更轻松而使用它们。当您的类层次结构变得更加复杂时,这可能会在将来对您造成伤害(注意:我并不是说您根本不应该使用中间类;-))。

那么对于这个具体案例我会做什么呢?我宁愿在抽象Item类中实现如下所示的内容:

public final boolean isConsumable() {
  return this instanceof Consumable;
}
Run Code Online (Sandbox Code Playgroud)

但也许我什至不会提供这样的方法,因为它item instanceof Consumable首先就和写作一样好。

我什么时候应该使用接口的默认方法?也许当接口有一个混合字符或者当实现对接口比抽象类更有意义时,例如,Consumable我可能会在那里提供作为默认方法的特定函数,而不是在任何伪实现类中,以便其他然后类可以再次从中扩展...我也非常喜欢以下关于 mixin 的答案(或者更确切地说是引用)

关于您的编辑:“Java 不允许多重继承” ...好吧,使用 mixins 类似多重继承的东西是可能的。您可以实现许多接口,并且接口本身也可以扩展许多其他接口。使用默认方法,您就拥有了可重用的东西:-)

那么,为什么default可以使用接口中的方法(或者不与接口定义本身相矛盾):

  • 提供已经满足大多数用例的简单或简单的实现(其中实现类可以提供特定的、专门的和/或优化的功能)
  • 当从所有参数和上下文中清楚该方法必须做什么时(并且没有合适的抽象类)
  • 对于模板方法,即当它们发出对抽象方法的调用来执行范围更广泛的某些工作时。典型的例子是Iterable.forEach,它使用 的抽象方法iterator()并将Iterable提供的操作应用于其每个元素。

谢费德里科·佩拉尔塔·沙夫纳的建议。

向后兼容性也是为了完整性,但与功能接口一样单独列出:默认实现还有助于在添加新功能时不破坏现有代码(通过抛出异常以使代码仍然保持编译或通过提供适用于所有实现类的适当实现)。对于函数式接口这种比较特殊的接口情况来说,默认方法是相当重要的。功能接口可以轻松地通过功能来增强,而功能本身不需要任何特定的实现。仅考虑Predicate一个例子..您提供test,但您还获得negate,or另外and(作为默认方法提供)。许多函数式接口通过默认方法提供额外的上下文函数。

  • @CrimsonMike 不一定。接口可以提供默认的、简单的、朴素的实现,然后一些子类可以用它们自己的特定的、专门的、优化的实现来覆盖这些简单的实现。 (3认同)
  • 然而,在极少数情况下,当从所有参数和值中清楚地了解该方法在大多数情况下将返回什么以及如果没有合适的抽象类时,我也可能在“正常”接口中拥有默认实现。 (2认同)
  • @CrimsonMike使用默认方法的另一个很好的理由是*模板*方法,即当它们发出对抽象方法的调用来执行一些范围更广泛的工作时。典型的例子是“Iterable.forEach”,它使用“Iterable”的抽象方法“iterator()”,并将提供的操作应用于迭代器返回的每个元素。 (2认同)
  • 我认为你总结得很好,这个答案对每个学习Java(初学者和中级)的人都非常有帮助。当我开始使用界面时,像这样的帖子和答案将是黄金 - 节省了我大量的时间。 (2认同)