Kotlin DSL 編程示例 ? KHttp

本文試圖以 KHttp 為例,簡要地說明 DSL 的減負(fù)能力和 Kotlin DSL 的一般寫法,希望能與讀到這篇文章的您一起分享代碼優(yōu)化的一些感悟。通讀全文大概需要花費(fèi)您 2 分鐘的時(shí)間。

嗯,欠下的總歸是要還的。在《Kotlin 帶你飛 ? 實(shí)戰(zhàn)篇》里筆者為自己立了一個(gè)實(shí)踐 DSL 的 Flag,拖了近兩個(gè)月,文章碼起。

相關(guān) Github 源碼 ? KHttp
版權(quán)聲明:本文為 frendy 原創(chuàng)文章,可以隨意轉(zhuǎn)載,但請(qǐng)務(wù)必在明確位置注明出處。


什么是 DSL

百度百科曰:領(lǐng)域特定語言,domain-specific languages,簡稱 DSL。

維基百科曰:A domain-specific language (DSL) is a computer language specialized to a particular application domain. This is in contrast to a general-purpose language (GPL), which is broadly applicable across domains.

是不是有點(diǎn)抽象,其實(shí) Html 就是 DSL 的代表,Java 就是 GPL 的代表。我們舉一個(gè)來自于網(wǎng)上的栗子:

用自然語言來描述我們的任務(wù)是,請(qǐng)列出 2011 年 11 月 20 日之后商務(wù)型和個(gè)人用的 13 寸的 A 品牌筆記本的交易額,不計(jì)入價(jià)格大于 10000 的 13 寸筆記本。

那么我們可以定義一個(gè) DSL 語言如下:

SUM (表.交易額) {
大小 : 13寸
用途 : 商務(wù)型和個(gè)人用
品牌 : A
Filter : 日期 > 2011-11-20
Without : 大小 = 13 and 價(jià)格 > 10000
}

DSL 會(huì)解析這個(gè)命令,然后讀取數(shù)據(jù)結(jié)構(gòu)和地址等配置文件,生成數(shù)據(jù)分析的代碼。

恩,這是一種簡化,一種對(duì)特定處理的封裝

以《領(lǐng)域特定語言》的作者 Fowler 的觀點(diǎn),DSL 首先是一種幫助用戶從一個(gè)系統(tǒng)中抽象出某些部分的工具。所以當(dāng)你意識(shí)到你需要一個(gè)組件,或者當(dāng)你已經(jīng)有了一個(gè)組件而你希望簡化操作它的方式的時(shí)候,DSL 是有用的。使用 DSL 確實(shí)提供了某些益處。DSL 不僅提高了代碼的易讀性,讓開發(fā)者可以和領(lǐng)域?qū)<腋玫慕涣?,而且是改變?zhí)行上下文的一種手段,例如:把邏輯從編譯時(shí)切換到運(yùn)行時(shí),或者當(dāng)命令式編程不是很合適的時(shí)候轉(zhuǎn)用聲明式計(jì)算模型。

恩,DSL 編程就是一種聲明式編程,直觀、容易理解和使用。

其實(shí)在使用 Android Studio 進(jìn)行開發(fā)的過程中,相信大家都已經(jīng)切身感受過 Gradle 的巨大威力。和 Maven 比,Gradle 的 DSL 是如此簡潔,在典型的 Maven 項(xiàng)目里,配置代碼肯定要超過上千行,所以 Kotlin DSL 是來給我們減負(fù)的。


什么是 KHttp

KHttp 是筆者實(shí)踐 DSL 的一個(gè)示例,是對(duì)于第三方網(wǎng)絡(luò)請(qǐng)求庫 OKHttp3 的 Kotlin DSL 封裝。其實(shí)網(wǎng)上許多朋友也都有過相關(guān)封裝了,簡單明了,在項(xiàng)目中也挺好用(捧臉)。

1. 如何使用

  • 在根目錄的 build.gradle 文件中添加 repositories 如下:
allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}
  • 在使用到的模塊添加依賴:
dependencies {
    compile 'com.github.frendyxzc:KHttp:0.0.2'
}
  • 用法如下,很像 js 是不是,更多細(xì)節(jié)可參考 README
http {
    url = url_get
    method = "get"
    onSuccess {
        jsonStr: String -> 
        callback.onSuccess(gson.fromJson(jsonStr, Resp::class.java).data)
    }
    onFail {
        e -> callback.onFail(e.message)
    }
}

2. 封裝原理

源碼先行:

fun http(init: KRequest.() -> Unit) {
    val wrap = KRequest()
    wrap.init()
    executeForResult(wrap)
}

class KRequest {
    var url: String? = null
    var method: String? = null
    var body: RequestBody? = null
    var timeout: Long = 10

    internal var _success: (String) -> Unit = { }
    internal var _fail: (Throwable) -> Unit = { }

    fun onSuccess(onSuccess: (String) -> Unit) {
        _success = onSuccess
    }
    fun onFail(onError: (Throwable) -> Unit) {
        _fail = onError
    }
}

這里筆者著重說明兩個(gè)自認(rèn)為可能存在疑問的點(diǎn)吧:

  • fun http(init: KRequest.() -> Unit)

其中 KRequest.() -> Unit 是擴(kuò)展函數(shù)的用法,也即是為 KRequest 類定義了一個(gè)名為 init() 的擴(kuò)展函數(shù),并將該函數(shù)對(duì)象作為 http() 的一個(gè)參數(shù)。

因此,下面的語句翻譯成類似 Java 這種 GPL 語言的表達(dá)方式其實(shí)是這樣的:

http {
    url = url_get
}
KRequest().init(url_get)

class KRequest {
    void init(String url) {
        this.url = url;
    }
}

至于 Kotlin 擴(kuò)展函數(shù),如不清楚,可參考筆者之前的 Kotlin 帶你飛系列文章。這里簡單舉個(gè)栗子,比如給 Context 添加 toast 函數(shù)如下:

fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
    Toast.makeText(this, message, duration).show()
}

可在任意地方通過 context 實(shí)例調(diào)用擴(kuò)展函數(shù)如下

context.toast("Hello frendy!")
  • executeForResult(wrap)

其實(shí)這里也沒什么疑問了,就是調(diào)用 OKHttp3 執(zhí)行網(wǎng)絡(luò)請(qǐng)求,同時(shí)用 Flowable 來管理各個(gè)請(qǐng)求:

private fun executeForResult(wrap: KRequest) {
    Flowable.create<Response>(
            { e -> e.onNext(onExecute(wrap)) },
            BackpressureStrategy.BUFFER
    ).subscribeOn(Schedulers.io()).subscribe(
            { resp -> wrap._success(resp.body()!!.string()) },
            { e -> wrap._fail(e) }
    )
}

private fun onExecute(wrap: KRequest): Response? {
    var req: Request? = null
    when (wrap.method) {
        "get", "Get", "GET" -> req = Request.Builder().url(wrap.url).build()
        "post", "Post", "POST" -> req = Request.Builder().url(wrap.url).post(wrap.body).build()
        "put", "Put", "PUT" -> req = Request.Builder().url(wrap.url).put(wrap.body).build()
        "delete", "Delete", "DELETE" -> req = Request.Builder().url(wrap.url).delete(wrap.body).build()
    }
    val http = OkHttpClient.Builder().connectTimeout(wrap.timeout, TimeUnit.SECONDS).build()
    val resp = http.newCall(req).execute()
    return resp
}


看到這里,有沒有躍躍欲試的感覺,DSL 的寫法這么干凈、這么簡潔,以至于不懂代碼的人都可以配置一下就跑起來是吧。嗯,但是也并不是所有場景都合適,至于怎么評(píng)判,還是聽 Fowler 的話吧,當(dāng)你意識(shí)到你需要一個(gè)組件,或者當(dāng)你已經(jīng)有了一個(gè)組件而你希望簡化操作它的方式的時(shí)候,DSL 是有用的,嗯,重要的話還是要重復(fù)重復(fù)的。


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

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

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