函数 tostring_Kotlin实战之Fuel的高阶函数


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)
}
2) 因为 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 = initialif (!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 |
| } |
+------------------------------+
至此,一个简单的拦截器功能就实现了,代码竟然如此简洁,感动!
参考
- 原文链接
- 拆轮子系列:拆 OkHttp by Piasy
- Kotlin中文文档
相关文章:

linux valgrind 安装和使用
linux valgrind 安装和使用 安装过程没这么复杂。 直接命令行: sudo apt-get install valgrind Valgrind 是个开源的工具,功能很多。例如检查内存泄漏工具—memcheck。 Valgrind 安装: sudo apt-get install valgrind Valgrind 命令介绍ÿ…

UIPopoverController在ARC环境下用法注意
在ARC环境下如果便用以下代码: [cpp] view plaincopyprint?UIViewController *viewTwo; viewTwo [[ViewTwo alloc] initWithNibName:"ViewTwo" bundle:nil]; UIPopoverController *popover; popover [[UIPopoverController alloc] initWithConten…

CPLD的分频语言
分频器在FPGA/CPLD设计中是不可缺少的一部分,这就包括分频系数是奇数和偶数的(我们称为奇分频和偶分频),而对于偶分频来说还有不同的分频方法,下面将给出具体的方法: 1、占空比不为50%的偶分频 占空比&…

彻底解决web开发中遇到的路径问题(上)
注:本文部分引用了网络上的文章,以及动力节点老师的讲解内容,感谢老师,嘻嘻。 为了举例方便,我新建了pathTest项目: 关于tomcat的配置,eclipse访问项目的路径一般是localhost:8080/projectName,…

关于Page翻页效果--Page View Controller
Page View Controllers你使用一个page view controller用page by page的方式来展示内容。一个page view controller管理一个self-contained视图架构。这个架构的父视图由page View controller管理,并且子视图由你提供的view Controllers管理。一,解析Pag…

linux平台下QtCreator中集成Valgrind系列工具
linux平台下QtCreator中集成Valgrind系列工具 ###1、valgrind 安装 valgrind 安装 2、打开QtCreator >> Analyze 你就会发现 这里已经有valgrind的相关选项了 如果没有的话, 在help >> about plugin >> C 中勾选 如图: 点击则可以直接运行…

python输入参数改变图形_Python基于Tensor FLow的图像处理操作详解
本文实例讲述了Python基于Tensor FLow的图像处理操作。分享给大家供大家参考,具体如下:在对图像进行深度学习时,有时可能图片的数量不足,或者希望网络进行更多的学习,这时可以对现有的图片数据进行处理使其变成一张新的…

CSS层叠样式
为了让网页元素的样式更加丰富,也为了让网页的内容和样式能拆分开,CSS由此思想而诞生,CSS是 Cascading Style Sheets 的首字母缩写,意思是层叠样式表。有了CSS,html中大部分表现样式的标签就废弃不用了,htm…

windows下 Source Monitor代码度量工具的使用
windows下 Source Monitor代码度量工具的使用 引用链接: https://www.cnblogs.com/xuehanyu/p/4520965.html 1.总体介绍 SourceMonitor是一款免费的软件,运行在Windows平台下。它可对多种语言写就的代码进行度量,包括C、C、C#、Java、VB、Delphi和HT…

MVVM 数据绑定
一、在 XAML 中创建绑定 定义源对象。 C# public class Dog {public string DogName { get; set; } }在 XAML 中创建对源对象的命名空间的引用。 XAML <UserControl x:Class"BindingXAML.Page" xmlns"http://schemas.microsoft.com/winfx/2006/xaml/pres…

linux配置文件怎么把某行后几个字符替换_vim(Linux运维)
一、vim使用介绍 介绍在linux系统中,大部分配置文件都是ASCII的纯文本形式存放的,所以我们在修改系统设置的时候使用简单的文本编辑软件就可以实现了,如果你使用过windows当中的word的话,那么你可能会感觉linux字符界面的文本编辑…

Debian 6.0 安装过程 及中文乱码
2019独角兽企业重金招聘Python工程师标准>>> Debian 6.0 安装过程 Debian 6.0 安装过程 转(一个别人自录的安装过程录相) http://v.youku.com/v_show/id_XMjUyMzY1OTIw.html 转(别人写的一个过程) http://hi.baidu.com/ljx_freebsd/blog/item/88d60c09da379da22edd…

git 提交丢失Warning, you are leaving 2 commits behind,
早上在自己的一个版本代码上编辑,提交commint,但是checkout到其他分支再checkout回来发现该的东西不见了, 幸好terminal还没有关掉,回看日志: Warning: you are leaving 2 commits behind, not connected toany of you…

一台支持vlan管理的交换机_关于交换机的VLAN技术你了解多少?
VLAN(虚拟局域网)是对连接到的第二层交换机端口的网络用户的逻辑分段,不受网络用户的物理位置限制而根据用户需求进行网络分段。一个VLAN可以在一个交换机或者跨交换机实现。VLAN可以根据网络用户的位置、作用、部门或者根据网络用户所使用的…

需要反射时使用dynamic
//使用dynamic的写法 dynamic fileExplorerData _currentFolder.FileExplorerData; var data fileExplorerData.InsertFromPath(newPath);//使用反射的写法 MethodInfo InsertMethod _currentFolder.FileExplorerData.GetType().GetMethod("InsertFromPath"); var…

Linux平台下QtCreator集成代码静态分析工具clang-tidy和Clazy
Linux平台下QtCreator集成代码静态分析工具clang-tidy和Clazy 原文连接: https://blog.csdn.net/wsj18808050/article/details/79824619 内容: QtCreator在前几天发布了4.6.0的版本,增加了两个非常棒的新功能,分别是Clang-Tidy和Clazy 官方…

JAVA swing初级教程(四)
附加的swing小部件(下) JOptionPane JOptionPane 是在 Swing 中类似“快捷方式”的东西。通常,作为 UI 开发人员,您需要向用户呈现快速信息,让用户了解错误和信息。甚至可能想得到一些快速数据,例如名称或数字。在 Swing 中&#…

Akka源码分析-Remote-发消息
上一篇博客我们介绍了remote模式下Actor的创建,其实与local的创建并没有太大区别,一般情况下还是使用LocalActorRef创建了Actor。那么发消息是否意味着也是相同的呢? 既然actorOf还是委托给了LocalActorRef,那么在本地创建的Actor…

用sed 给文本文件加行号
看例子: [rootlocalhost tmp]# sed test.txt 1 tsttst tsttsttst 2 west gao 3 west abces [rootlocalhost tmp]# sed test.txt | sed N;s/\n/\t/ 1 tsttst tsttsttst 2 west gao 3 west abces [rootlocalhost tmp]# N的解释: N&am…

Qt 编译一直死循环问题
Qt 编译一直死循环问题 有时候Qt编译项目时, 一直编不过,查看一下编译窗口,发现一直在循环 输出如下: C:\soft\Qt5.11\5.11.1\mingw53_32\bin\qmake.exe -o Makefile ..\..\..\..\PalmQtLib\PalmQtLib\PalmQtLib.pro -spec win32-g "…

路由器运行python脚本_写个Python脚本来登录小米路由器
这个脚本写起来难度并不是很大,博主还是一步步的分析下,这样思路会比较清晰,下次遇到类似系统脚本写起来也更快速。好了,一起来分析分析。首先看下小米路由器的登录界面可以看到只需要输入密码即可登录,博主这里为了演…

PHP定时执行任务的实现
2019独角兽企业重金招聘Python工程师标准>>> ignore_user_abort();//关掉浏览器,PHP脚本也可以继续执行. set_time_limit(0);// 通过set_time_limit(0)可以让程序无限制的执行下去 $interval60*30;// 每隔半小时运行 do{//这里是你要执行的代码 sleep($i…

Spring事务管理 与 SpringAOP
1,Spring事务的核心接口 Spring事务管理的实现有许多细节,如果对整个接口框架有个大体了解会非常有利于我们理解事务,下面通过讲解Spring的事务接口来了解Spring实现事务的具体策略。 Spring事务管理涉及的接口的联系如下: 1.…

iso镜像文件烧写到U盘
iso镜像文件烧写到U盘 windows rufus-3.1.exe 百度云盘链接:https://pan.baidu.com/s/16p1O4lXMVTUltTvCm0DnHA 提取码:inzj 文件格式一般选择默认的就行,如果起不来,就换一个, linux 1、dd命令 2、系统自带 usb-creator-gtk工具 命令行输入&#x…

webgl 游戏_30个令人惊叹的WebGL示例和演示
WebGl仍在增长,尽管大多数现代浏览器都支持它,但它也可能需要在旧的浏览器上工作。在本文中,我遇到了很多WebGL的示例和演示,它们可以增进您对这项新技术的理解。因此,请坐下来放松身心,使用最新的浏览器&a…

IE8下的VML显示问题解决方案
最近在维护一个使用VML画曲线的网站,在不同的IE下浏览效果不一样,特别是在IE8下,出现莫名其妙的样式显示问题: 1.曲线不可见!在IE9或IE7下,曲线正常绘制,但是在IE8下,不见坐标轴和曲…

创新工场有哪些失败项目?不要只看着成功
创新工场有哪些失败项目?不要只看着成功 李开复 ,创新工场CEO回答:失败或碰到挑战的项目也不少。这里不点名,不谈细节,但是谈谈碰到什么挑战(有些已经失败,有些还在努力):…

彻底解决Linux索引节点(inode)占用率高的告警
今天邮箱里发现有一封某服务器inode使用率发生告警的邮件 登录到服务器上df -i查看,发现/路径下91%,磁盘使用率却不高,猜测可能是某个目录下的小文件过多,进而造成inode占用率过高,但不清楚根路径下各文件夹里的文件数…

镜像打包工具clonezilla
镜像打包工具clonezilla clonezilla 百度云盘链接:https://pan.baidu.com/s/1LOEPqNE9O0Z4QJmNExlgeA 提取码:zlso 使用方法: 1、将镜像直接烧入U盘 2、U盘启动

python数据分析设置_Python 数据分析系列之如何安装和设置 Python
由于人们用 Python 所做的事情不同,所以没有一个普适的 Python 及其插件包的安装方案,接下来我将详细介绍各个操作系统上 Python 科学计算环境部署。我推荐免费的 Anaconda 安装包,Anaconda 提供 Python 2.7 和 3.6 两个版本,以后…