介绍
我已经阅读了多篇关于在SO上实现接口和抽象类的文章。我找到了一篇特别想在这里链接 - 链接 - 接口与默认方法 vs 抽象类, 它涵盖了同样的问题。作为被接受的答案,建议在可能的情况下使用接口的默认方法。但是下面的评论指出“这个功能对我来说更像是一个hack”,解释了我的问题。
默认方法被引入以使接口的实现更加灵活 - 当接口发生变化时,实现类不一定需要(重新)编写代码。因此,仅使用接口的默认方法来实现所有实现类中的方法-引用:“对我来说更像是一个hack”。
我的考试例子:
类概述:
- Item - 所有物品的抽象超类
- Water - 可消耗的物品
- Stone - 不可消耗的物品
- Consumable - 一些可消耗物品的方法的接口(这些方法必须由所有实现类重写)
结合这些:
Water是一个Item并且实现了Consumable; Stone也是一个Item,但没有实现Consumable。
我的考试
我想实现一个所有物品都必须实现的方法。因此,我在类Item中声明了这个方法的签名。
protected abstract boolean isConsumable();
//return true if class implements (or rather "is consumable") Consumable and false in case it does not
现在我有几个选择:
如上所述,当扩展应用程序时,这将是不切实际或高度低效的。
- 在每个Item子类中手动实现该方法(当扩展应用程序时,这绝对不可能)。
这是其他帖子建议的解决方案-我看到了使用这种方式实现的优点:
- 将该方法作为默认方法实现在接口内部,以便Consumable类已经实现了超类Item所需的方法。
但是,在我看来,它仍然似乎与我在介绍中提到的默认方法的原始想法相矛盾。此外,当扩展应用程序并引入更多共享所有Consumables的相同实现的方法(例如示例方法引用 -“这个新功能的好处是,在你之前被迫使用抽象类来进行方便的方法时,从而限制了实现者的单一继承,现在你可以只使用接口和最少量的实现工作来获得真正干净的设计。”链接
isConsumable()
)时,接口将实现多个默认方法,这与接口不实现实际方法的想法相矛盾。介绍使用子超类而不是接口 - 例如将Consumable类作为Item的抽象子类和Water的超类。
这提供了在Item中编写方法的默认情况(例如:
isConsumable() //return false
),然后在子超类中重写此方法的机会。但问题在于:随着应用程序的扩展和引入更多的子超类(如Consumable类),实际项目将开始扩展超过一个子超类。这可能并不是件坏事,因为对于接口也需要做同样的事情,但它使继承树变得复杂 - 例如:现在一个项目可能扩展一个名为ALayer2的子超类,它是ALayer1的子超类,后者又是Item(layer0)的超类。引入另一个超类(因此与Item具有相同的层次结构) - 例如将Consumable类作为抽象类引入,该类将是Water的另一个超类。这意味着Water将必须扩展Item&Consumable。
这个选项提供了灵活性。可以为新超类创建全新的继承树,同时仍能看到Item的实际继承关系。但我发现这种结构在实际类中的实现以及之后使用这些类的过程中存在缺点 - 例如:如果Consumable可以有不适用于项目的子类,那么如何说Consumable是Item。整个转换过程可能会引起头痛,比选项3的结构更加头痛。
什么是实现此结构的正确选项?
- 这是我列出的选项之一吗?
- 它是那些选项的变体吗?
- 还是另一个我尚未考虑到的选项?
我选择了一个非常简单的例子 - 请在回答时记住将可扩展性考虑为将来的实现。 预先感谢您的任何帮助。
编辑#1
Java不允许多重继承。 这将影响选项4。 使用多个接口(因为你可以实现多个)可能是一种解决方法,不幸的是,再次需要默认方法,这正是我最初试图避免的实现类型。 链接 - 可能的解决方案的多重继承问题
Item
知道它的实例是否为Consumable
听起来很奇怪。个人而言,我宁愿没有这样的方法,在需要时使用theInstance instanceof Consumable
。 - sp00mItem::isConsumable
和一个单独的接口Consumable
吗?为什么Item
需要知道Consumable
的存在呢? - Eugenedefault
方法来实现超类的abstract
方法。因为Item
没有实现Consumable
,所以Consumable
中的default
方法与Item
中具有相同签名的已声明方法之间没有关系。因此你有两个不相关的方法。Java 设计者通过让非接口类的方法始终获胜来解决这个潜在的错误源,即使它是abstract
的也是如此。这也兼容 Java 8 之前的代码。 - Holgerclass A { void foo() {} }
和class B { void foo() {} }
。当然,在没有多重继承的情况下,它们之间没有问题。因此,只有涉及“默认”方法的情况,您可能会在一个类中遇到这样的方法,要么通过从两个不相关的接口继承它们(然后,除非您覆盖它们,否则会出现错误),要么通过从一个接口和超类继承一个(然后,您将获得超类方法)。 - Holger