这是称为“ Functional Java by Example”的系列文章的第5部分。
在上一部分中,我们停止了对文档的变异,并返回了数据的副本。 现在,我们需要移走一些I / O。
如果您是第一次来,最好是从头开始阅读。 它有助于了解我们从何处开始以及如何在整个系列中继续前进。
这些都是这些部分:
- 第1部分–从命令式到声明式
- 第2部分–讲故事
- 第3部分–不要使用异常来控制流程
- 第4部分–首选不变性
- 第5部分–将I / O移到外部
- 第6部分–用作参数
- 第7部分–将失败也视为数据
- 第8部分–更多纯函数
我将在每篇文章发表时更新链接。 如果您通过内容联合组织来阅读本文,请查看我博客上的原始文章。
每次代码也被推送到这个GitHub项目 。
将I / O移到外面
还记得我们以前留下的东西吗?
class FeedHandler {Webservice webserviceDocumentDb documentDbvoid handle(List<Doc> changes) {changes.findAll { doc -> isImportant(doc) }.each { doc ->createResource(doc).thenAccept { resource ->documentDb.update(setToProcessed(doc, resource))}.exceptionally { e ->documentDb.update(setToFailed(doc, e))}}}private CompletableFuture<Resource> createResource(doc) {webservice.create(doc)}private boolean isImportant(doc) {doc.type == 'important'}private Doc setToProcessed(doc, resource) {doc.copyWith(status: 'processed',apiId: resource.id)}private Doc setToFailed(doc, e) {doc.copyWith(status: 'failed',error: e.message)}}
我在本系列的每个部分中发展的示例是某种“提要处理程序”,用于处理文档。
处理效果如何?
- 一份或多份文件进来
- 如果文档“很重要”,则将其保存到Web服务API中,该API将为其创建并返回资源
- 如果成功,则将文档标记为已处理
- 如果失败,则将文档标记为失败
- 最终,文档将在数据库中更新
Web服务可以是REST服务(因为我们在谈论资源 ),数据库可以是CouchDB或MongoDB的文档存储(因为我们在谈论文档 ),但这并不重要。
重要的是通常在任何系统中都涉及一些I / O(输入/输出)。 从文件系统读取信息,将信息存储到数据库中,在Web服务之间通过网络进行通信。
正如我们在上一期文章中所见,我们希望我们的功能尽可能纯净 ,没有任何副作用。 不幸的是,真正的系统必须与外界交互才能有意义。
我们还如何获取输入到系统中的信息,或向用户输出什么呢? I / O的一些示例是:
- 文件系统访问
- 网络插座
- HTTP请求
- JDBC操作
- 启动线程
- 系统时钟访问
我们已经通过setToProcessed
/ setToFailed
方法摆脱了对数据库的访问,方法是将其上移到调用链上一步,但是它仍在FeedHandler
。
我们能做的最好的就是将I / O移到系统外部。
我们可以做的最明显的改变是完全摆脱数据库,而只是从handle()
返回新的更新文档。
摆脱数据库
更改
.thenAccept { resource ->documentDb.update(setToProcessed(doc, resource))
}
.exceptionally { e ->documentDb.update(setToFailed(doc, e))
}
至
.thenApply { resource ->setToProcessed(doc, resource)
}
.exceptionally { e ->setToFailed(doc, e)
}
摆脱documentDb
。
我们只是在通话链的更远处返回所有修改过的文档。 这就是为什么我们还必须……
…摆脱虚无
从更改返回类型
void handle(...)
至
List<Doc> handle(...)
因此处理过的文档会一直返回到外部。
并不是说我们不再与任何数据库进行任何交互,而是不再需要我们的FeedHandler
组件! 通过将任何I / O移至系统的外围,中间的所有内容都可以尽可能地纯净。
还记得Haskell,这是一种“纯”功能语言吗? 从“ 学到了伟大的Haskell” :
事实证明,Haskell实际上拥有一个非常聪明的系统来处理具有副作用的功能,这些功能将我们程序的纯净部分和不纯净的部分整齐地分开了,这就像在与之交谈一样,可以完成所有肮脏的工作。键盘和屏幕。 将这两个部分分开,我们仍然可以推理我们的纯程序,并利用纯净提供的所有功能,例如惰性,健壮性和模块化,同时与外界进行有效的通信。
当它在90年代被发明时,它引入了IO
monad来处理I / O。 任何函数,例如从外部读取,都必须使用返回类型IO
,该类型实际上是由编译器检查的。
这有一些好处,例如Haskell编译器在重新排序所有非IO
代码以进行优化方面具有一定的自由度。 从纯函数和I / O :
因为纯函数代码就像代数,所以编译器可以将所有非IO函数视为数学方程式。 这有点类似于关系数据库如何优化查询。
在Java中,我们没有针对这些事情的特定编译器支持,但是有一些事情我们可以照顾好自己。
记住: void
是一个沉Kong。 任何返回void
方法要么没有意义,要么具有副作用,例如写入显示,网络,文件或数据库,即与外部系统的交互。 代替执行I / O作为副作用,而是向调用方返回一个值,以描述与外部系统的交互。
现在就这样!
翻译自: https://www.javacodegeeks.com/2018/11/functional-java-example-move-outside.html