首页 > 其他分享 >Kotlin实战之Fuel的高阶函数

Kotlin实战之Fuel的高阶函数

时间:2023-01-04 14:35:50浏览次数:67  
标签:acc 函数 Kotlin Request next requestInterceptors Fuel 高阶


Kotlin实战之Fuel的高阶函数_Kotlin

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

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.InlineOnly
public inline operator fun <T> MutableCollection<in T>.plusAssign(element: T) {
this.add(element)
}

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

fun <T> 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) }

​foldRight​​​ 是 ​​List​​ 的一个扩展函数,先来看声明:

/**
* Accumulates value starting with [initial] value and applying [operation] from right to left to each element and current accumulator value.
*/
public inline fun <T, R> List<T>.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 |
| } |
+------------------------------+

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

参考


标签:acc,函数,Kotlin,Request,next,requestInterceptors,Fuel,高阶
From: https://blog.51cto.com/u_15929756/5988611

相关文章

  • 今晚(10月20日)网络高阶:愿景(二)
    今晚(10月20日)网络高阶:愿景(二)愿景目标和系统功能是多对多的:一个愿景目标可能会带来系统的多个功能,一个系统功能可能覆盖了多个愿景目标。如图2-13中,“提高防汛决策准确度”是......
  • 业务建模高阶(2)业务用例图和业务序列图
    根据前面讲的知识要点,一看图3-21右侧,护士在组织边界外面,就知道不对了。但是,要求建模人员按照业务用例的定义做时,有人就会说:我的系统就是这个功能,我已经知道了,我还要考虑其他......
  • “用例规约高阶”幻灯片第二部分
    ......
  • react高阶组件
    1.首先介绍高阶函数基本概念:函数可以作为参数被传递:函数可以作为返回值输出: 2.高阶组件组件作为参数被传递,返回值是一个组件高阶组件是一个函数案例:将A组件作为公共组件,BC......
  • kotlin中var与val的区别
    Kotlin中有常见的两种变量定义:varvalval相当于java中定义常量时加了一个final,而var就是正常定义变量定义一个数值和string很好理解,在注意定义对象的时候,不要搞错了,val一......
  • Kotlin 在 Android 开发中的 16 个建议
    译者简介:ASCE1885, 《Android高级进阶》作者。小密圈:Android高级进阶​原文链接:https://savvyapps.com/blog/kotlin-tips-android-development。SavvyApps在2016年底开......
  • Kotlin
    packagecom.example.test02importkotlin.math.maxfunmain(args:Array<String>){vala=10//常量Intvaraa=10L//变量Longvarb="string......
  • 第二章 探究新语言,快速入门Kotlin编程
    第二章探究新语言,快速入门Kotlin编程2.1Kotlin简介略2.2如何运行Kotlin代码(1)打开HelloWorld项目,找到MainActivity所在的位置(2)在MainActivity的同级包结构下创建一......
  • 使用 Spring Boot 和 Kotlin 构建 Web 应用程序
    本教程向您展示如何通过结合弹簧启动和科特林.如果您从Kotlin开始,您可以通过阅读参考文档,跟随在线KotlinKoans教程或只是使用Spring框架参考文档现在在Kotlin中提供......
  • Python学习笔记--高阶技巧(二)
    Socket服务端开发基本步骤如下:socket客户端开发基本步骤如下:1、创建socket对象2、连接到服务器3、发送消息4、接收返回消息5、关闭连接正则表达式基础方法......