使用循环和收集管道进行重构:第二部分,嵌套循环

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

编者注:“使用循环和收集管道进行重构”是 Martin Fowler 的一个由四部分组成的系列。您可以 在此处 阅读第一部分。

循环是处理集合的经典方式,但随着编程语言中一流函数的更多采用, 集合管道 成为一种有吸引力的替代方法。在这篇文章中,我通过一系列小示例研究了将循环重构为收集管道。


嵌套循环 - 图书读者

对于第二个示例,我将重构一个简单的双重嵌套循环。我假设我有一个允许读者阅读书籍的在线系统。我有一个数据服务,可以告诉我每个读者在特定的一天阅读了哪些书。该服务返回一个散列,其键是读者的标识符,值是书籍标识符的集合

接口数据服务...


 Map<String, Collection<String>> getBooksReadOn(Date date);

对于这个例子,我将切换到 Java,因为我厌倦了首字母大写的方法名称

这是循环


 Map<String, Collection<String>> getBooksReadOn(Date date);

我将使用我通常的第一步,即将 Extract Variable 应用于循环集合


 Map<String, Collection<String>> getBooksReadOn(Date date);

这样的举动让我很高兴 IntelliJ 的 自动重构让我免于输入那种粗糙的类型表达式。

一旦我将初始集合放入一个变量中,我就可以处理循环行为的元素。所有工作都在条件中进行,所以我将从该条件中的第二个子句开始并将其逻辑移至 过滤器


 Map<String, Collection<String>> getBooksReadOn(Date date);

使用 Java 的流库,管道必须以终端(例如收集器)结束

另一个子句更难移动,因为它引用内部循环变量。此子句正在测试以查看映射条目的值是否包含也在方法参数的书籍列表中的任何书籍。我可以通过使用集合交集来做到这一点。 Java 核心类不包含集合交集方法。我可以通过使用一种常见的面向集合的 Java 插件来获得,例如 Guava Apache Commons 。由于这是一个简单的教学示例,我将编写一个粗略的实现。

类实用程序...


 Map<String, Collection<String>> getBooksReadOn(Date date);

这在这里有效,但对于任何实质性项目,我都会使用公共库。

现在我可以将子句从循环中移到管道中。


 Map<String, Collection<String>> getBooksReadOn(Date date);

现在循环所做的就是返回映射条目的键,所以我可以通过向管道添加 映射操作 来终止循环的其余部分


 Map<String, Collection<String>> getBooksReadOn(Date date);

然后我可以在 result 上使用 Inline Temp


 Map<String, Collection<String>> getBooksReadOn(Date date);

查看交集的用法,我发现它相当复杂,我必须在阅读时弄清楚它的作用——这意味着我应该提取它。

类实用程序...


 Map<String, Collection<String>> getBooksReadOn(Date date);

班级服务...


 Map<String, Collection<String>> getBooksReadOn(Date date);

当您需要做这样的事情时,面向对象的方法处于劣势。在对象的静态实用方法和普通方法之间切换很尴尬。对于某些语言,我有办法让它读起来像流类上的方法,但我在 Java 中没有这个选项。

尽管存在这个问题,我仍然发现管道版本更容易理解。我可以将过滤器组合成一个联合,但我通常发现将每个过滤器理解为单个元素更容易。