使用接口的默认方法实现方法 - 自相矛盾?
Implementing methods using default methods of interfaces - Contradictory?
简介
我在 SO 上阅读了多篇关于实现接口和抽象 classes 的 posts。我发现了一个特别的问题,我想在这里 link - Link - Interface with default methods vs abstract class,它涵盖了相同的问题。作为公认的答案,建议在可能的情况下使用接口的默认方法。但是这个答案下面的评论 "this feature feels more like a hack to me" 解释了我的问题。
引入了默认方法以使接口的实现更加灵活 - 当接口更改时,实现 classes 中不一定需要(重新)编写代码。因此,使用一个接口的默认方法只是为了在所有实现classes中实现一个方法——引用:"feels more like a hack to me"。
我的考试示例:
类概览:
- 项目 - 所有项目的抽象超级class
- 水 - 消耗品
- 石头 - 不可消耗的物品
- Consumable - 与一些用于消耗品的方法的接口(所有实现 classes 都必须重写这些方法)
结合这些:
Water是一个Item并且实现了Consumable; Stone 也是一个 Item,不实现 Consumable。
我的考试
我想实现一个所有项目都必须实现的方法。因此,我在class项中声明签名。
protected abstract boolean isConsumable();
//return true if class implements (or rather "is consumable") Consumable and false in case it does not
Quick edit: I am aware that instanceof might solve this particular example - if possible think of a more complicated example that makes it necessary to implement the method in the first place. (Thanks to Sp00m and Eugene)
现在我有几个选择:
- Implement the method by hand in every single subclass of Item (this is definitely not possible when scaling the application).
如上所述,在扩展应用程序时,这将是不切实际或效率极低的。
- Implementing the method inside of the interface as a default method so the Consumable classes already implement the method which is required by the superclass Item.
这是其他人推荐的解决方案 post - 我看到了以这种方式实施它的优点:
Quote - "The good thing about this new feature is that, where before you were forced to use an abstract class for the convenience methods, thus constraining the implementor to single inheritance, now you can have a really clean design with just the interface and a minimum of implementation effort forced on the programmer." Link
但在我看来,这与我在介绍中提到的默认方法的最初想法似乎仍然矛盾。此外,当扩展应用程序并引入更多对所有消耗品共享相同实现的方法时(如示例方法 isConsumable()
),接口将实现多个默认方法,这与接口不实现实际方法的想法相矛盾。
- Introducing sub-superclasses instead of an interface - for example the class Consumable as an abstract subclass of Item and superclass of Water.
它提供了为 Item 中的方法编写默认情况的机会(示例:isConsumable() //return false
),然后在子超级class 中覆盖它。这里出现的问题是:当扩展应用程序并引入更多子超classes(作为Consumableclass)时,实际的Item会开始扩展多个子超class ].这可能不是一件坏事,因为也有必要对接口做同样的事情,但它使继承树变得复杂 - 示例:一个项目现在可能扩展一个 subsuperclass ALayer2,它是一个 sub-superclass 的 ALayer1 扩展了 Item (layer0).
- Introducing another superclass (thus same layer as Item) - for example the class Consumable as an abstract class which will be another superclass of Water. That means that Water would have to extend Item & Consumable
此选项提供了灵活性。可以为新的 superclass 创建一个全新的继承树,同时仍然能够看到 Item 的实际继承。但我发现的缺点是在实际 classes 中实现此结构并在以后使用它们 - 示例:我怎么能说:当 Consumable 能够具有 sub[ 时,Consumable 是一个 Item =82=]es 不适用于项目。整个转换过程可能会让人头疼——比方案3的结构更让人头疼。
问题
实施此结构的正确选择是什么?
- 它是我列出的选项之一吗?
- 它是那些的变体吗?
- 还是我还没有考虑过的另一种选择?
我选择了一个非常简单的示例 - 请在回答时牢记未来实施的可扩展性。提前感谢您的帮助。
编辑#1
Java 不允许多重继承。这将影响选项 4。
使用多个接口(因为您可以实现多个接口)可能是一种解决方法,不幸的是,默认方法将再次成为必需,这正是我最初一直试图避免的实现方式。 Link - Multiple inheritance problem with possible solution
我遗漏了选项 5(或者我没看正确):
在 Item
自身内部提供方法。
假设消耗品可以通过 Consumable
界面识别,这就是我不能推荐你列出的大部分要点的原因:
第一个(即在每个子 class 中实现它)对于像 this instanceof Consumable
这样简单的东西来说太多了。第二个可能没问题,但不会是我的第一选择。第三个和第四个完全不推荐。如果我只能给出一个建议,那么它可能会考虑两次继承并且永远不要使用中间 classes 仅仅因为它们在某个时间点让你的生活更轻松。当您的 class 层次结构变得更加复杂时,这可能会在将来伤害您(注意:我并不是说您根本不应该使用中间 classes ;-))。
那么我会针对这个具体案例做些什么呢?我宁愿在摘要中实现如下内容 Item
class:
public final boolean isConsumable() {
return this instanceof Consumable;
}
但也许我什至不会提供这样的方法,因为它和首先写 item instanceof Consumable
一样好。
什么时候可以使用接口的默认方法?也许当接口具有混合字符时,或者当实现对接口更有意义时,那么抽象 class,例如Consumable
的一个特定功能我可能会在那里提供默认方法而不是在任何伪实现 class 中只是为了让其他 classes 可以再次从它扩展......我也很喜欢following answer (or rather the quote) regarding mixin.
关于您的编辑:"Java does not allow multiple inheritance" ...好吧,使用 mixins 可以实现类似于多重继承的东西。您可以实现许多接口,接口本身也可以扩展许多其他接口。使用默认方法,您就拥有了可重用的东西:-)
那么,为什么接口中的 default
方法可以使用(或者不与接口定义本身相矛盾):
- 提供已经满足大多数用例的简单或简单的实现(其中实现 classes 可以提供特定的、专门的 and/or 优化功能)
- 当从所有参数和上下文中清楚该方法必须做什么时(并且没有合适的抽象 class 到位)
- 对于模板方法,即当它们发出对抽象方法的调用以执行一些范围更广的工作时。典型示例是
Iterable.forEach
,它使用 Iterable
的抽象方法 iterator()
,并将提供的操作应用于其每个元素。
感谢 Federico Peralta Schaffner 的建议。
向后兼容性也是为了完整性,但与功能接口一样单独列出:
默认实现还有助于在添加新函数时不破坏现有代码(通过抛出异常以使代码仍然保持编译或通过提供适用于所有实现 classes 的适当实现)。
对于函数式接口,这是一种特殊的接口情况,默认方法非常关键。功能接口可以很容易地通过功能来增强,而功能本身不需要任何特定的实现。仅以 Predicate
为例。您提供 test
,但您还获得 negate
、or
和 and
(作为默认方法提供)。许多功能接口通过默认方法提供额外的上下文功能。
简介
我在 SO 上阅读了多篇关于实现接口和抽象 classes 的 posts。我发现了一个特别的问题,我想在这里 link - Link - Interface with default methods vs abstract class,它涵盖了相同的问题。作为公认的答案,建议在可能的情况下使用接口的默认方法。但是这个答案下面的评论 "this feature feels more like a hack to me" 解释了我的问题。
引入了默认方法以使接口的实现更加灵活 - 当接口更改时,实现 classes 中不一定需要(重新)编写代码。因此,使用一个接口的默认方法只是为了在所有实现classes中实现一个方法——引用:"feels more like a hack to me"。
我的考试示例:
类概览:
- 项目 - 所有项目的抽象超级class
- 水 - 消耗品
- 石头 - 不可消耗的物品
- Consumable - 与一些用于消耗品的方法的接口(所有实现 classes 都必须重写这些方法)
结合这些:
Water是一个Item并且实现了Consumable; Stone 也是一个 Item,不实现 Consumable。
我的考试
我想实现一个所有项目都必须实现的方法。因此,我在class项中声明签名。
protected abstract boolean isConsumable();
//return true if class implements (or rather "is consumable") Consumable and false in case it does not
Quick edit: I am aware that instanceof might solve this particular example - if possible think of a more complicated example that makes it necessary to implement the method in the first place. (Thanks to Sp00m and Eugene)
现在我有几个选择:
- Implement the method by hand in every single subclass of Item (this is definitely not possible when scaling the application).
如上所述,在扩展应用程序时,这将是不切实际或效率极低的。
- Implementing the method inside of the interface as a default method so the Consumable classes already implement the method which is required by the superclass Item.
这是其他人推荐的解决方案 post - 我看到了以这种方式实施它的优点:
Quote - "The good thing about this new feature is that, where before you were forced to use an abstract class for the convenience methods, thus constraining the implementor to single inheritance, now you can have a really clean design with just the interface and a minimum of implementation effort forced on the programmer." Link
但在我看来,这与我在介绍中提到的默认方法的最初想法似乎仍然矛盾。此外,当扩展应用程序并引入更多对所有消耗品共享相同实现的方法时(如示例方法 isConsumable()
),接口将实现多个默认方法,这与接口不实现实际方法的想法相矛盾。
- Introducing sub-superclasses instead of an interface - for example the class Consumable as an abstract subclass of Item and superclass of Water.
它提供了为 Item 中的方法编写默认情况的机会(示例:isConsumable() //return false
),然后在子超级class 中覆盖它。这里出现的问题是:当扩展应用程序并引入更多子超classes(作为Consumableclass)时,实际的Item会开始扩展多个子超class ].这可能不是一件坏事,因为也有必要对接口做同样的事情,但它使继承树变得复杂 - 示例:一个项目现在可能扩展一个 subsuperclass ALayer2,它是一个 sub-superclass 的 ALayer1 扩展了 Item (layer0).
- Introducing another superclass (thus same layer as Item) - for example the class Consumable as an abstract class which will be another superclass of Water. That means that Water would have to extend Item & Consumable
此选项提供了灵活性。可以为新的 superclass 创建一个全新的继承树,同时仍然能够看到 Item 的实际继承。但我发现的缺点是在实际 classes 中实现此结构并在以后使用它们 - 示例:我怎么能说:当 Consumable 能够具有 sub[ 时,Consumable 是一个 Item =82=]es 不适用于项目。整个转换过程可能会让人头疼——比方案3的结构更让人头疼。
问题
实施此结构的正确选择是什么?
- 它是我列出的选项之一吗?
- 它是那些的变体吗?
- 还是我还没有考虑过的另一种选择?
我选择了一个非常简单的示例 - 请在回答时牢记未来实施的可扩展性。提前感谢您的帮助。
编辑#1
Java 不允许多重继承。这将影响选项 4。 使用多个接口(因为您可以实现多个接口)可能是一种解决方法,不幸的是,默认方法将再次成为必需,这正是我最初一直试图避免的实现方式。 Link - Multiple inheritance problem with possible solution
我遗漏了选项 5(或者我没看正确):
在 Item
自身内部提供方法。
假设消耗品可以通过 Consumable
界面识别,这就是我不能推荐你列出的大部分要点的原因:
第一个(即在每个子 class 中实现它)对于像 this instanceof Consumable
这样简单的东西来说太多了。第二个可能没问题,但不会是我的第一选择。第三个和第四个完全不推荐。如果我只能给出一个建议,那么它可能会考虑两次继承并且永远不要使用中间 classes 仅仅因为它们在某个时间点让你的生活更轻松。当您的 class 层次结构变得更加复杂时,这可能会在将来伤害您(注意:我并不是说您根本不应该使用中间 classes ;-))。
那么我会针对这个具体案例做些什么呢?我宁愿在摘要中实现如下内容 Item
class:
public final boolean isConsumable() {
return this instanceof Consumable;
}
但也许我什至不会提供这样的方法,因为它和首先写 item instanceof Consumable
一样好。
什么时候可以使用接口的默认方法?也许当接口具有混合字符时,或者当实现对接口更有意义时,那么抽象 class,例如Consumable
的一个特定功能我可能会在那里提供默认方法而不是在任何伪实现 class 中只是为了让其他 classes 可以再次从它扩展......我也很喜欢following answer (or rather the quote) regarding mixin.
关于您的编辑:"Java does not allow multiple inheritance" ...好吧,使用 mixins 可以实现类似于多重继承的东西。您可以实现许多接口,接口本身也可以扩展许多其他接口。使用默认方法,您就拥有了可重用的东西:-)
那么,为什么接口中的 default
方法可以使用(或者不与接口定义本身相矛盾):
- 提供已经满足大多数用例的简单或简单的实现(其中实现 classes 可以提供特定的、专门的 and/or 优化功能)
- 当从所有参数和上下文中清楚该方法必须做什么时(并且没有合适的抽象 class 到位)
- 对于模板方法,即当它们发出对抽象方法的调用以执行一些范围更广的工作时。典型示例是
Iterable.forEach
,它使用Iterable
的抽象方法iterator()
,并将提供的操作应用于其每个元素。
感谢 Federico Peralta Schaffner 的建议。
向后兼容性也是为了完整性,但与功能接口一样单独列出:
默认实现还有助于在添加新函数时不破坏现有代码(通过抛出异常以使代码仍然保持编译或通过提供适用于所有实现 classes 的适当实现)。
对于函数式接口,这是一种特殊的接口情况,默认方法非常关键。功能接口可以很容易地通过功能来增强,而功能本身不需要任何特定的实现。仅以 Predicate
为例。您提供 test
,但您还获得 negate
、or
和 and
(作为默认方法提供)。许多功能接口通过默认方法提供额外的上下文功能。