博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Kotlin实战之Fuel的高阶函数
阅读量:6263 次
发布时间:2019-06-22

本文共 5566 字,大约阅读时间需要 18 分钟。

是一个用 Kotlin 写的网络库,与 OkHttp 相比较,它的代码结构比较简单,但是它的巧妙之处在于充分利用了 ,所以代码看上去干净利落。

OkHttp 使用了一个 interceptor chain 来实现拦截器的串联调用,由于 Java 语言( JDK ≤ 7)本身的局限性,所以实现代码比较臃肿,可读性也不友好。当然,RxJava 再加上 retrolambda 这种 backport 的出现,一定程度上了缓解了这种尴尬,但是 Kotlin 天生具备的声明式写法又使得 Java 逊色了很多。

我们知道,拦截器本质上是一个责任链模式(chain of responsibility)的实现,我们通过具体代码来学习一下 Kotlin 究竟是如何利用高阶函数实现了拦截器功能。

首先定义一个 MutableList 用于存储拦截器实例:

val requestInterceptors:   MutableList<((Request) -> Request) -> ((Request) -> Request)>    = mutableListOf()复制代码

注意,Kotlin 的类型系统明确区分了 mutable 和 immutable,默认的 List 类型是 immutable。

requestInterceptors 的元素类型是一个:

((Request) -> Request) -> ((Request) -> Request)复制代码

作为元素类型的高阶函数,其参数也是一个高阶函数 (Request) -> Request, 同时,返回值也是高阶函数 (Request) -> Request

然后,我们给 requestInterceptors 定义一个增加元素的方法:

fun addRequestInterceptor(  interceptor: ((Request) -> Request) -> ((Request) -> Request)) {    requestInterceptors += interceptor}复制代码

addRequestInterceptor 的参数类型

(Request) -> Request) -> ((Request) -> Request)复制代码

requestInterceptors 的元素类型一致。

注意,这里又出现了一个 Kotlin 有而 Java 没有的语言特性:操作符重载。

我们没有调用 requestInterceptors.add(interceptor),而是用了一个 plusAssign 的操作符 +=(MutableCollections.kt 中定义的操作符重载):

/** * Adds the specified [element] to this mutable collection. */@kotlin.internal.InlineOnlypublic inline operator fun 
MutableCollection
.plusAssign(element: T) { this.add(element)}复制代码

那么,此时应该定义一个拦截器的函数实例了:

fun 
loggingRequestInterceptor() = { next: (T) -> T -> { t: T -> println(t.toString()) next(t) } }复制代码

loggingRequestInterceptor 是一个函数,它的返回值是一个 lambda 表达式(即高阶函数):

{ next: (T) -> T ->    { t: T ->        println(t.toString())        next(t)    }}复制代码
  1. 这个 lambda 的参数next: (T) -> T(参数名是 next,参数类型是 (T) -> T),返回值是另一个 lambda 表达式:
{ t: T ->    println(t.toString())    next(t)}复制代码
  1. 因为 lambda 本身是一个函数字面量(function literal),它的类型通过函数本身可以推到得出,如果我们用一个变量来引用这个 lambda 的话,变量的类型是 (T) -> T

由1、2两点可知,loggingRequestInterceptor() 的返回值是一个 lambda 表达式,它的参数是 (T) -> T,返回值也是 (T) -> T

这里的泛型函数略抽象,我们来看一个具体化的函数:

fun cUrlLoggingRequestInterceptor() =        { next: (Request) -> Request ->            { r: Request ->                println(r.cUrlString())                next(r)            }        }复制代码

同理,cUrlLoggingRequestInterceptor() 函数的参数为 (Request) -> Request、返回值为 (Request) -> Request

拦截器都定义好了,那么应该如何调用呢?Kotlin 一行代码搞定?::

requestInterceptors.foldRight({ r: Request -> r }) { f, acc -> f(acc) }复制代码

foldRightList 的一个扩展函数,先来看声明:

/** * Accumulates value starting with [initial] value and applying [operation] from right to left to each element and current accumulator value. */public inline fun 
List
.foldRight(initial: R, operation: (T, acc: R) -> R): R { var accumulator = initial if (!isEmpty()) { val iterator = listIterator(size) // 让迭代器指向最后一个元素的末尾 while (iterator.hasPrevious()) { accumulator = operation(iterator.previous(), accumulator) } } return accumulator}复制代码

函数功能总结为一句话:从右往左,对列表中的每一个元素执行 operation 操作,每个操作的结果是下一次操作的入参,第一次 operation 的初始值是 initial

回头来看拦截器列表 requestInterceptors 如何执行了 foldRight

requestInterceptors.foldRight({ r: Request -> r }) { f, acc -> f(acc) }复制代码

参数 inital: R 的实参是 { r: Request -> r },一个函数字面量,没有执行任何操作,接收 r 返回 r

参数 operation: (T, acc: R) -> R 可接收一个 lambda,所以它的实参 {f, acc -> f(acc)} 可以位于圆括号之外。f 的泛型是 T,具体类型是

((Request) -> Request) -> ((Request) -> Request)复制代码

acc 的类型通过 initial: R 的实参 { r: Request -> r } 可以推到得出——(Request) -> Request

OK,语法完全没毛病,再来看语义。

+---------------------+| { r: Request -> r } | ---> 初始值,命名为 *fun0*+---------------------+           |           |          \|/    fun0 作为参数传递给 requestInterceptors 最右的 f(最后一个元素)+----------------------------------|------------------------f---------------------|-+| cUrlLoggingRequestInterceptor(): ((Request) -> Request) -> ((Request) -> Request) |+----------------------------------|----------------------------------------------|-+           |           |                  f 返回结果:           |                  +-----------------------------+           |                  | { r: Request ->             |           |                  |     println(r.cUrlString()) |           |                  |     fun0(r)                 |           |                  | }                           |           |                  +-----------------------------+           |                                    命名为 *fun1*           |            \|/   fun1 作为参数,传递给倒数第二个 f+----------------------------------|-----------------------f--------------------|-+| loggingRequestInterceptor(): ((Request) -> Request) -> ((Request) -> Request)   |+----------------------------------|--------------------------------------------|-+           |           |                  f 返回结果:           |                  +-----------------------------+           |                  | { r: Request ->             |           |                  |     println(1.toString())   |           |                  |     fun1(r)                 |           |                  | }                           |           |                  +-----------------------------+           |                                    命名为 *fun2*          \|/   将 fun2 解体:+------------------------------+| { r: Request ->              ||     println(r.toString())    ||     println(r.cUrlString())  | 类型为:(Request) -> request|     r                        || }                            |+------------------------------+复制代码

至此,一个简单的拦截器功能就实现了,代码竟然如此简洁,感动!

原文链接:http://liangfei.me/2018/04/10/kotlin-fuel-interceptor/

参考

转载地址:http://iykpa.baihongyu.com/

你可能感兴趣的文章
使用iview的组件 Table 表格,有固定列,设置其中一个列适应屏幕大小
查看>>
Vue学习笔记1
查看>>
用户输入一个网址到页面展示内容的这段时间内,浏览器和服务器都发生了生么事情?...
查看>>
动手搞一个Promise
查看>>
[case32]alibaba限流组件Sentinel实战
查看>>
用python来给图片加水印
查看>>
【跃迁之路】【550天】程序员高效学习方法论探索系列(实验阶段307-2018.08.09)...
查看>>
link和@import的区别浅析
查看>>
vscode 相关
查看>>
nodejs 全自动使用 Tinypng (免费版,无需任何配置)压缩图片
查看>>
彻底理解Java中的基本数据类型转换(自动、强制、提升)
查看>>
在CentOS中安装redis5.0
查看>>
重构-改善既有代码的设计(六)--重新组织函数
查看>>
panic: time: missing Location in call to Time.In
查看>>
在K8S集群中一步步构建一个复杂的MySQL数据库
查看>>
前端每日实战:15# 视频演示如何用纯 CSS 创作条形图,不用任何图表库
查看>>
浅谈 Angular 项目实战
查看>>
初学Linux指导(三)
查看>>
C++入门教程(8):if 语句
查看>>
Tampermonkey的使用
查看>>