本文試圖以 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ù)的。
