前言
最近看到DSL這個東西,不由的覺得里面可以利用Kotlin的一些特性能簡化代碼,所以具體來看看它是如何實作的,
正文
首先一上來就說原理或許對于不熟悉Kotlin的來說會感覺有點突兀,所以我準備從頭梳理一下,
約定
Kotlin的約定我們在平時開發中肯定用到過,不過我們沒有仔細去注意這個名詞而已,約定的概念就是:使用與常規方法呼叫語法不同的、更簡潔的符號,呼叫著有著特殊命名的函式,
這里提取2個關鍵點,一個是更簡潔的符號呼叫,一個是特殊命名的函式,說白了就是讓函式呼叫更加簡潔,
比如我們最熟悉的集和呼叫 [index] 來 替代 get(index),我們自己也來定義個類,來實作一下這個約定:
data class TestBean(val name: String,val age: Int){
//定義非常簡單 使用operator多載運算子get方法
operator fun get(index : Int): Any{
return when(index) {
0 -> name
1 -> age
else -> name
}
}
}
然后我們在使用時:
//這里就可以使用 [] 來替換 get來簡化呼叫方法了
val testBean = TestBean("zyh",20)
testBean.get(0)
testBean[0]
invoke約定
和上面的get約定一樣,[] 就是呼叫 get 方法的更簡潔的方式,這里有個invoke約定,它的作用就是讓物件像函式一樣呼叫方法,下面直接來個例子:
data class TestBean(val name: String,val age: Int){
//多載定義invoke方法
operator fun invoke() : String{
return "$name - $age"
}
}
定義完上面代碼后,我們來進行使用:
val testBean = TestBean("zyh",20)
//正常呼叫
testBean.invoke()
//約定后的簡化呼叫
testBean()
這里會發現testBean物件可以呼叫invoke方法是正常呼叫,但是也可以testBean()直接來呼叫invoke方法,這就是invoke約定的作用,讓呼叫invoke方法更簡單,
invoke約定和函式式型別
既然了解了invoke約定,我們來和lambda結合起來,
我們知道函式型別其實就是實作了FunctionN介面的類,然后當函式型別是函式型別時,這時傳遞給它一個lambda,lambda就會被編譯成FunctionN的匿名內部類(當然是非行內的),然后呼叫lambda就變成了一次FunctionN介面的invoke呼叫,
還是看個例子代碼:
//定義代碼
class TestInvoke {
//高階函式型別變數
private var mSingleListener: ((Int) -> Unit)? = null
//設定變數
public fun setSingleListener(listener:((Int) -> Unit)?){
this.mSingleListener = listener
}
//
fun testRun() {
//呼叫invoke函式
mSingleListener?.invoke(100)
//使用invoke約定,省去invoke
if (mSingleListener != null){
mSingleListener!!(100)
}
}
}
定義完上面回呼變數后,我們來使用這個回呼,由于我們知道高階函式其實是實作了FunctionN介面的類,也就是實作了:
//注意,這里介面的方法就是invoke
public interface Function1<in P1, out R> : Function<R> {
/** Invokes the function with the specified argument. */
public operator fun invoke(p1: P1): R
}
那我也就可以直接使用下面代碼來傳遞引數:
val function1 = object: Function1<Int,Unit> {
override fun invoke(p1: Int) {
Logger.d("$p1")
}
}
testInvoke.setSingleListener(function1)
這里看起來合情合理,因為在testRun函式中我們呼叫了invoke函式,把100當做引數,然后這個100會被回呼到function1中,但是我們傳遞lambda時呢:
val testInvoke = TestInvoke()
testInvoke.setSingleListener { returnInt ->
Logger.d("$returnInt")
}
上面代碼傳遞lambda和傳遞一個類的實體效果是一樣的,只不過這里只是一段代碼塊,沒有顯示的呼叫invoke啥的,所以這就是一個特性,當lambda被用作引數被函式呼叫時,也就可以看成是一次invoke的自動呼叫,
invoke在DSL中的實踐:Gradle依賴
這里我們為什么要說這個invoke依賴呢,很大的原因就是它在一些DSL中有很好的用法,這里我們就來看個Gradle依賴的使用,
我們很常見下面代碼:
dependencies {
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
//...
}
這里我們都很習以為常,感覺這里很像配置項,而不像是代碼,其實這個也是一段代碼,只不過是這種風格,那這種風格如何實作呢,我們來簡單實作一下:
class DependencyHandler{
//編譯庫
fun compile(libString: String){
Logger.d("add $libString")
}
//定義invoke方法
operator fun invoke(body: DependencyHandler.() -> Unit){
body()
}
}
上面代碼寫完后,我們便可以有下面3種呼叫方式:
val dependency = DependencyHandler()
//呼叫invoke
dependency.invoke {
compile("androidx.core:core-ktx:1.6.0")
}
//直接呼叫
dependency.compile("androidx.core:core-ktx:1.6.0")
//帶接受者lambda方式
dependency{
compile("androidx.core:core-ktx:1.6.0")
}
由此可見,上面代碼第三種方式便是我們在Gradle組態檔中常見的一種,這里其實就2個關鍵點,一個是定義invoke函式,一個是定義帶接受者的lambda,呼叫時省去this即可,
總結
其實關于invoke約定和帶接受者lambda的寫法現在越來越流行了,比如之前的anko庫,現在的compose庫都是這種宣告式的寫法,看完原理后,就會發現其實還是很方便的,
后續開始研究compose的時候,再來補充一波,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/404170.html
標籤:其他
上一篇:安卓微信本地資料庫解密
