Kotlin實(shí)戰(zhàn)之Fuel的高階函數(shù)

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)
    }
}
  1. 這個(gè) lambda 的參數(shù)next: (T) -> T(參數(shù)名是 next,參數(shù)類型是 (T) -> T),返回值是另一個(gè) lambda 表達(dá)式:
{ t: T ->
    println(t.toString())
    next(t)
}
  1. 因?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) }

foldRightList 的一個(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)!

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 人生苦多,快來 Kotlin ,快速學(xué)習(xí)Kotlin! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,703評論 9 118
  • 原文鏈接:https://github.com/EasyKotlin 值就是函數(shù),函數(shù)就是值。所有函數(shù)都消費(fèi)函數(shù),...
    JackChen1024閱讀 6,347評論 1 17
  • 本文是在學(xué)習(xí)和使用kotlin時(shí)的一些總結(jié)與體會(huì),一些代碼示例來自于網(wǎng)絡(luò)或Kotlin官方文檔,持續(xù)更新... 對...
    竹塵居士閱讀 3,489評論 0 8
  • 家居合肥,住在天鵝湖畔。每次逛湖,總有一種感恩的意識(shí)纏繞心頭。遠(yuǎn)看霓虹燈七彩閃爍,近觀湖水波光粼粼,我的心...
    丁懷超閱讀 313評論 0 0
  • 12月之后,連著好幾天沒見到太陽。天氣陰沉,寒風(fēng)肆虐。 從食堂回到宿舍的我,臉色蒼白,雙腿一刻也不敢停...
    逆風(fēng)的漂流瓶閱讀 257評論 0 0

友情鏈接更多精彩內(nèi)容