Fuel 是一個(gè)用 Kotlin 寫的網(wǎng)絡(luò)庫,與 OkHttp 相比較,它的代碼結(jié)構(gòu)比較簡單,但是它的巧妙之處在于充分利用了 Kotlin 的語言特性,所以代碼看上去干凈利落。
OkHttp 使用了一個(gè) interceptor chain 來實(shí)現(xiàn)攔截器的串聯(lián)調(diào)用,由于 Java 語言( JDK ≤ 7)本身的局限性,所以實(shí)現(xiàn)代碼比較臃腫,可讀性也不友好。當(dāng)然,RxJava 再加上 retrolambda 這種 backport 的出現(xiàn),一定程度上了緩解了這種尷尬,但是 Kotlin 天生具備的聲明式寫法又使得 Java 遜色了很多。
我們知道,攔截器本質(zhì)上是一個(gè)責(zé)任鏈模式(chain of responsibility)的實(shí)現(xiàn),我們通過具體代碼來學(xué)習(xí)一下 Kotlin 究竟是如何利用高階函數(shù)實(shí)現(xiàn)了攔截器功能。
首先定義一個(gè) MutableList 用于存儲(chǔ)攔截器實(shí)例:
val requestInterceptors:
MutableList<((Request) -> Request) -> ((Request) -> Request)>
= mutableListOf()
注意,Kotlin 的類型系統(tǒng)明確區(qū)分了 mutable 和 immutable,默認(rèn)的 List 類型是 immutable。
requestInterceptors 的元素類型是一個(gè)高階函數(shù):
((Request) -> Request) -> ((Request) -> Request)
作為元素類型的高階函數(shù),其參數(shù)也是一個(gè)高階函數(shù) (Request) -> Request, 同時(shí),返回值也是高階函數(shù) (Request) -> Request。
然后,我們給 requestInterceptors 定義一個(gè)增加元素的方法:
fun addRequestInterceptor(
interceptor: ((Request) -> Request) -> ((Request) -> Request)) {
requestInterceptors += interceptor
}
addRequestInterceptor 的參數(shù)類型
(Request) -> Request) -> ((Request) -> Request)
與 requestInterceptors 的元素類型一致。
注意,這里又出現(xiàn)了一個(gè) Kotlin 有而 Java 沒有的語言特性:操作符重載。
我們沒有調(diào)用 requestInterceptors.add(interceptor),而是用了一個(gè) 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)
}
那么,此時(shí)應(yīng)該定義一個(gè)攔截器的函數(shù)實(shí)例了:
fun <T> loggingRequestInterceptor() =
{ next: (T) -> T ->
{ t: T ->
println(t.toString())
next(t)
}
}
loggingRequestInterceptor 是一個(gè)函數(shù),它的返回值是一個(gè) lambda 表達(dá)式(即高階函數(shù)):
{ next: (T) -> T ->
{ t: T ->
println(t.toString())
next(t)
}
}
- 這個(gè) lambda 的參數(shù)是
next: (T) -> T(參數(shù)名是next,參數(shù)類型是(T) -> T),返回值是另一個(gè) lambda 表達(dá)式:
{ t: T ->
println(t.toString())
next(t)
}
- 因?yàn)?lambda 本身是一個(gè)函數(shù)字面量(function literal),它的類型通過函數(shù)本身可以推到得出,如果我們用一個(gè)變量來引用這個(gè) lambda 的話,變量的類型是
(T) -> T。
由1、2兩點(diǎn)可知,loggingRequestInterceptor() 的返回值是一個(gè) lambda 表達(dá)式,它的參數(shù)是 (T) -> T,返回值也是 (T) -> T。
這里的泛型函數(shù)略抽象,我們來看一個(gè)具體化的函數(shù):
fun cUrlLoggingRequestInterceptor() =
{ next: (Request) -> Request ->
{ r: Request ->
println(r.cUrlString())
next(r)
}
}
同理,cUrlLoggingRequestInterceptor() 函數(shù)的參數(shù)為 (Request) -> Request、返回值為 (Request) -> Request。
攔截器都定義好了,那么應(yīng)該如何調(diào)用呢?Kotlin 一行代碼搞定??::
requestInterceptors.foldRight({ r: Request -> r }) { f, acc -> f(acc) }
foldRight 是 List 的一個(gè)擴(kuò)展函數(shù),先來看聲明:
/**
* 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) // 讓迭代器指向最后一個(gè)元素的末尾
while (iterator.hasPrevious()) {
accumulator = operation(iterator.previous(), accumulator)
}
}
return accumulator
}
函數(shù)功能總結(jié)為一句話:從右往左,對列表中的每一個(gè)元素執(zhí)行 operation 操作,每個(gè)操作的結(jié)果是下一次操作的入?yún)?,第一?operation 的初始值是 initial。
回頭來看攔截器列表 requestInterceptors 如何執(zhí)行了 foldRight:
requestInterceptors.foldRight({ r: Request -> r }) { f, acc -> f(acc) }
參數(shù) inital: R 的實(shí)參是 { r: Request -> r },一個(gè)函數(shù)字面量,沒有執(zhí)行任何操作,接收 r 返回 r。
參數(shù) operation: (T, acc: R) -> R 可接收一個(gè) lambda,所以它的實(shí)參 {f, acc -> f(acc)} 可以位于圓括號之外。f 的泛型是 T,具體類型是
((Request) -> Request) -> ((Request) -> Request)
acc 的類型通過 initial: R 的實(shí)參 { r: Request -> r } 可以推到得出——(Request) -> Request。
OK,語法完全沒毛病,再來看語義。
+---------------------+
| { r: Request -> r } | ---> 初始值,命名為 *fun0*
+---------------------+
|
|
\|/ fun0 作為參數(shù)傳遞給 requestInterceptors 最右的 f(最后一個(gè)元素)
+----------------------------------|------------------------f---------------------|-+
| cUrlLoggingRequestInterceptor(): ((Request) -> Request) -> ((Request) -> Request) |
+----------------------------------|----------------------------------------------|-+
|
| f 返回結(jié)果:
| +-----------------------------+
| | { r: Request -> |
| | println(r.cUrlString()) |
| | fun0(r) |
| | } |
| +-----------------------------+
| 命名為 *fun1*
|
\|/ fun1 作為參數(shù),傳遞給倒數(shù)第二個(gè) f
+----------------------------------|-----------------------f--------------------|-+
| loggingRequestInterceptor(): ((Request) -> Request) -> ((Request) -> Request) |
+----------------------------------|--------------------------------------------|-+
|
| f 返回結(jié)果:
| +-----------------------------+
| | { r: Request -> |
| | println(1.toString()) |
| | fun1(r) |
| | } |
| +-----------------------------+
| 命名為 *fun2*
\|/ 將 fun2 解體:
+------------------------------+
| { r: Request -> |
| println(r.toString()) |
| println(r.cUrlString()) | 類型為:(Request) -> request
| r |
| } |
+------------------------------+
至此,一個(gè)簡單的攔截器功能就實(shí)現(xiàn)了,代碼竟然如此簡潔,感動(dòng)!