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

时间:2018-09-03 11:38:02

标签: java inheritance interface java-8 default-method

简介

我在SO上已经阅读了多篇有关实现接口和抽象类的文章。我特别想找到一个我想在这里链接的人-Link - Interface with default methods vs abstract class,它涵盖了相同的问题。作为公认的答案,建议尽可能使用接口的默认方法。但是此答案下方的注释指出“此功能对我来说更像是一种黑客”,解释了我的问题。

已引入默认方法,以使接口的实现更加灵活-更改接口时,实现类中并不一定需要(重新)编写代码。因此,使用接口的默认方法只是为了在所有实现的类中实现一种方法-引用:“给我的感觉更像是黑客”。

我的考试示例:

课程概述:

  • 项目-所有项目的抽象超类
  • 水-易耗品
  • 石头-不可消耗的物品
  • 易耗品-与某些易耗品方法的接口(所有实现类都必须覆盖这些方法)

结合这些:

水是一种物品,可以消耗;石头也是物品,不会实现消耗品。

我的考试

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

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

快速编辑:我知道instanceof可能会解决此特定示例-如果可能,请考虑一个更复杂的示例,该示例使必须首先实现该方法。 (感谢Sp00m和Eugene)

现在我有几种选择:

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

如上所述,在扩展应用程序时,这是不切实际的或效率很低的。

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

这是另一篇文章推荐的解决方案-我看到了以这种方式实现的优势:

  

语录-“此新功能的好处是,在您被迫将抽象类用于便利方法之前,从而将实现者约束为单一继承,现在您只需使用介面,并让开发人员省下最少的执行工作。” Link

但是我认为,这似乎仍然与我在引言中提到的默认方法的原始思想相矛盾。此外,在扩展应用程序并引入更多对所有消耗品共享相同实现的方法时(如示例方法isConsumable()),该接口将实现几种默认方法,这与接口未实现实际方法的思想相矛盾。

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

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

  
      
  1. 引入另一个超类(因此与Item处于同一层)-例如,Consumable类作为抽象类,它将是Water的另一个超类。这意味着水将不得不扩展物品和消耗品
  2.   

此选项提供了灵活性。可以为新的超类创建一个全新的继承树,同时仍然能够看到Item的实际继承。但是我发现的缺点是该结构在实际类中的实现,并在以后使用它们-示例:我将如何说:消耗品是项,而消耗品将具有不用于项的子类。整个转换过程可能会引起头痛-比选择3的结构更有可能。

问题

实施此结构的正确选择是什么?

  • 这是我列出的选项之一吗?
  • 是这些的变体吗?
  • 还是我还没有想到的另一种选择?

我选择了一个非常简单的示例-回答时,请牢记将来实现的可伸缩性。感谢您的任何提前帮助。

编辑#1

Java不允许多重继承。这将影响选项4。 使用多个接口(因为您可以实现多个接口)可能是一种解决方法,但是很遗憾,再次需要使用default-method,这正是我本来尝试避免的那种实现。 Link - Multiple inheritance problem with possible solution

1 个答案:

答案 0 :(得分:4)

我缺少选项5(或者我未正确阅读): 在Item本身内部提供方法。

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

那么我将如何处理此特定情况?我宁愿在抽象的Item类中实现类似以下的内容:

public final boolean isConsumable() {
  return this instanceof Consumable;
}

但是也许我什至都不会提供像最初写item instanceof Consumable一样好的方法。

何时将使用默认的接口方法代替?也许当接口具有一个混合字符或当实现对接口比抽象类更有意义时,例如Consumable的特定功能,我可能会在此处提供默认方法,而不是在任何伪实现类中提供,只是为了让其他类可以再次从中扩展...我也很喜欢following answer (or rather the quote) regarding mixin

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

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

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

感谢Federico Peralta Schaffner的建议。

向后兼容性也是出于完整性考虑,但与功能接口分开列出: 添加新功能时,默认实现还有助于不破坏现有代码(通过仅抛出异常以使代码仍保持编译状态或提供适用于所有实现类的适当实现)。 对于功能性接口(这是一种特殊的接口情况),默认方法至关重要。可以通过功能轻松增强功能接口,而功能本身不需要任何特定的实现。仅以Predicate为例。您提供了test,但您还另外获得了negateorand(作为默认方法提供)。许多功能接口通过默认方法提供其他上下文功能。