inheritance,组合和默认方法

通常承认通过inheritance扩展接口的实现不是最佳实践,并且该组合(例如,从头开始再次实现接口)更加可维护。

这是有效的,因为接口契约强制用户实现所有所需的function。 但是在java 8中,默认方法提供了一些可以“手动”覆盖的默认行为。 请考虑以下示例:我想设计一个用户数据库,该数据库必须具有List的function。 出于效率目的,我选择通过ArrayList来支持它。

public class UserDatabase extends ArrayList{} 

这通常不被认为是一种很好的做法,如果实际上希望列表具有完整的function并遵循通常的“合成而不是inheritance”的座右铭,那么人们更愿意:

 public class UserDatabase implements List{ //implementation here, using an ArrayList type field, or decorator pattern, etc. } 

但是,如果不注意,则不需要覆盖某些方法,例如spliterator(),因为它们是List接口的默认方法。 问题是,List的spliterator()方法执行得比ArrayList的spliterator()方法差得多,后者已针对ArrayList的特定结构进行了优化。

这迫使开发人员

  1. 请注意,ArrayList有自己的,更有效的spliterator()实现,并手动覆盖他自己的List实现的spliterator()方法或者
  2. 使用默认方法会失去大量性能。

所以问题是:在这种情况下,人们应该更喜欢构成而不是inheritance吗?

在开始考虑性能之前,我们总是应该考虑正确性,即在您的问题中我们应该考虑使用inheritance而不是委托所暗示的内容。 这个EclipseLink / JPA问题已经说明了这一点 。 由于inheritance,如果尚未填充延迟填充列表,则排序(同样适用于流操作)不起作用。

因此,我们必须在专业化,覆盖新的default方法,在inheritance情况下完全中断以及default方法不能在委托情况下以最大性能工作的可能性之间进行权衡。 我想,答案应该是显而易见的。

由于您的问题是关于新的default方法是否会改变这种情况,因此应该强调的是,与以前甚至不存在的方法相比,您所谈论的是性能下降。 让我们留在sort榜样。 如果使用委托并且不覆盖default排序方法,则default方法的性能可能低于优化的ArrayList.sort方法,但在Java 8之前,后者不存在,并且未针对ArrayList优化的算法是标准行为。

因此,当您不覆盖default方法时,您不会在Java 8下失去与委托的性能,您只是没有获得更多。 我认为,由于其他改进,性能仍然优于Java 7(没有default方法)。

Stream API不容易比较,因为在Java 8之前不存在API。但是,很明显类似的操作,例如,如果你手动实现减少,除了通过你的委托列表的Iterator之外别无选择。要防止remove()尝试,因此包装ArrayList Iterator ,或使用size()get(int)委托给支持List 。 因此,没有预先default方法API可以表现出比Java 8 API的default方法更好的性能的default ,因为过去没有ArrayList特定的优化。

也就是说,您可以通过以不同方式使用组合来改进您的API设计:不要让UserDatabase实现List 。 只需通过访问方法提供List 。 然后,其他代码不会尝试流式传输UserDatabase实例,而是覆盖访问器方法返回的列表。 返回的列表可以是只读包装器 ,它提供JRE本身提供的最佳性能,并在可行的default小心覆盖default方法。

我真的不明白这里的大问题。 即使没有扩展, 可以使用ArrayList备份UserDatabase通过委派获得性能。 您无需对其进行扩展即可获得性能。

 public class UserDatabase implements List{ private ArrayList list = new ArrayList(); // implementation ... // delegate public Spliterator() spliterator() { return list.spliterator(); } } 

你的两点并没有改变这一点。 如果你知道“ArrayList有自己的,更有效的spliterator()实现”,那么你可以将它委托给你的后台实例,如果你不知道,那么默认方法会处理它。

我仍然不确定实现List接口是否真的有意义,除非您明确地创建了一个可重用的Collection库。 更好地为这样的一次性创建自己的API,通过inheritance(或接口)链不会带来未来的问题。

我不能为每种情况提供建议,但对于这种特殊情况,我建议不要实施ListUserDatabase.set(int, User)的目的是什么? 您真的想用全新的用户替换支持数据库中的第i个条目吗? 那么add(int, User)呢? 在我看来,您应该将其实现为只读列表(在每个修改请求上抛出UnsupportedOperationException )或仅支持一些修改方法(如add(User)支持,但add(int, User)不支持)。 但后一种情况会让用户感到困惑。 最好提供更适合您任务的修改API。 至于读取请求,可能最好返回一个用户流:

我建议创建一个返回Stream的方法:

 public class UserDatabase { List list = new ArrayList<>(); public Stream users() { return list.stream(); } } 

请注意,在这种情况下,您将来可以完全自由地更改实施。 例如,将ArrayList替换为TreeSetConcurrentLinkedDeque或其他。

选择很简单,根据您的要求。

– 以下只是一个用例。 来说明差异。

如果你想要一个不会保留重复的列表并且要做一大堆与ArrayList截然不同的事情,那么就没有使用扩展ArrayList因为你是从头开始编写所有东西。

在上面你应该Implement List 。 但是,如果您只是优化ArrayList的实现,那么您应该复制粘贴整个ArrayList实现并遵循优化而不是extending ArrayList 。 为什么,因为多层次的实施使得某人试图解决问题变得困难。

例如:一台4GB Ram作为父母的计算机,而Child正在使用8 GB RAM。 如果父级有4 GB而新的Child有4 GB可以制作8 GB,这是很糟糕的。 而不是具有8 GB RAM实现的子代。

在这种情况下我会建议composition 。 但它会根据情景而改变。

通常承认通过inheritance扩展接口的实现不是最佳实践,并且该组合(例如,从头开始再次实现接口)更易于维护。

我认为这根本不准确。 当然,在很多情况下,组合比inheritance更受欢迎,但在很多情况下,inheritance比组合更受欢迎!

特别重要的是要意识到实现类的inheritance结构不需要看起来像API的inheritance结构。

有没有人真的相信,例如,当编写像Java一样的图形库时,每个实现类应该重新实现paintComponent()方法? 事实上,设计的一个主要原因是,当为新类编写绘制方法时,可以调用super.paint()并确保绘制层次结构中的所有元素,以及处理涉及与本机接口进一步交互的复杂性。在树上。

普遍接受的是,扩展不在您控制范围内且不是为了支持inheritance而设计的类是危险的,并且在实现发生变化时可能会引发烦人的错误。 (如果您保留更改实施的权利,请将您的课程标记为最终课程!)。 我怀疑Oracle会在ArrayList实现中引入重大变化! 如果你尊重它的文件,你应该没事….

这就是设计的优雅。 如果他们认为ArrayList存在问题,他们将编写一个新的实现类,类似于当天替换Vector时,并且不需要引入重大更改。

===============

在你当前的例子中,有效的问题是:为什么这个类存在?

如果您正在编写一个扩展list接口的类,那么它实现了哪些其他方法? 如果它没有实现新方法,那么使用ArrayList有什么问题?

当你知道答案时,你会知道该怎么做。 如果答案“我想要一个基本上是列表的对象,但有一些额外的便利方法来操作该列表”,那么我应该使用组合。

如果答案是“我想从根本上改变列表的function”,那么你应该使用inheritance,或者从头开始实现。 一个示例可能是通过重写ArrayList的add方法来实现不可修改的列表以引发exception。 如果您对这种方法感到不舒服,您可以考虑通过扩展AbstractList来实现,它实际上是为了inheritance而最大限度地减少重新实现的工作。