Kotlin DSL
什么是DSL
Domain Special Language
DSL是領域特定語言话侄,與通用語言不通基括,他只管他的領域,如:SQL河哑、正則表達式避诽;
特點:
一般DSL都是非常簡潔的;所以DSL璃谨,一般趨向于聲明式沙庐、這樣就需要解釋,也就是效率有些影響佳吞;
一般的編程語言基本是命令式的拱雏,定義了確定的步驟;
一般的DSL很難與通用編程語言的宿主應用結合起來底扳,
gradle對應的Groovy是動態(tài)編程語言铸抑;DSL是有自己的結構的,DSL方法調用由DSL結構規(guī)定衷模;比如:SQL鹊汛;
Kotlin 中dsl 的特點:
Kotlin允許構建整潔API的功能包括:擴展函數(shù)、中綴調用阱冶、lambda柒昏、簡明語法和運算符重載;
- 完全靜態(tài)類型熙揍,優(yōu)勢:類型檢查,IDE提示等氏涩;
- 他是內部DSL届囚,語法兼容;不完全獨立是尖,但保留了獨立語法DSL優(yōu)點意系;
解決DSL的一些問題;
2. 帶接收者的Lambda
帶接收者的Lambda是Kotlin的一大特性饺汹,可以使用一個結構來構建API,擁有結構是區(qū)分DSL與普通API的關鍵特征蛔添;
我們來看下面3個函數(shù)
2.1 buildString、with 和 apply 函數(shù)
buildString函數(shù)
```
val abc = buildString {
for (alpha in 'A'..'Z') {
append(alpha)
}
}
println(abc)
```
with函數(shù)
```
val s = with(sb) {
for(a in 'a'..'z') {
append(a)
}
this.toString()
}
println(s)
```
apply 函數(shù)
```
val l = StringBuilder().apply {
for(a in 'a'..'z') {
append(a)
}
}.toString()
println(l)
```
一起過一下他們的方法簽名;
2.2 帶接收者的lambda和擴展函數(shù)類型
來創(chuàng)建一個自己的 mybuildString
函數(shù)
```
fun mybuildString(action: (StringBuilder) -> Unit) : String {
val sb = StringBuilder()
action(sb)
return sb.toString()
}
fun main(args: Array<String>) {
// 調用的使用需要it
val s = mybuildString {
it.append("Hello")
it.append("World")
}
println(s)
}
```
很好理解上面的代碼迎瞧,但是調用時候夸溶,需要傳it
,不夠簡潔,我們來改一下:
需要將lambda轉換成帶接收者的lambda
將接收者的特殊狀態(tài)賦予lambda參數(shù)的一個凶硅,這樣就可以不需要任何修飾符就能直接調用他的成員缝裁;
```
// 定義帶接收者的lambda的類型參數(shù)
fun mybuildString2(action: StringBuilder.() -> Unit) : String {
val sb = StringBuilder()
sb.action()
return sb.toString()
}
```
這里傳遞的是一個帶接收者的lambda作為參數(shù)(匿名擴展函數(shù)),可以去掉lambda函數(shù)體中的 it
變化:用擴展函數(shù)類型取代了普通函數(shù)類型來聲明參數(shù)足绅;
StringBuilder.() -> Unit 替代了 (StringBuilder) -> Unit,
這個特殊的類型(StringBuilder)叫做接收者類型捷绑,傳遞給lambda的這個類型的值叫做接收者對象
```
接收者類型 參數(shù)類型 返回類型
String. (Int,Int) -> Unit
```
上面的是一個擴展函數(shù)類型,接收者是String氢妈;
實際上,一個擴展函數(shù)類型粹污,描述了一個可以被當做擴展函數(shù)來調用的代碼塊
不是將參數(shù)傳給lambda,而是像調用擴展函數(shù)那樣調用lambda; 上面的的 action
不是String類的方法首量,他是一個函數(shù)類型的參數(shù)壮吩,但是可以調用擴展函數(shù)一樣的語法調用他;
看一下圖:
函數(shù)的實參(帶lambda的接收者),對應于擴展函數(shù)類型的形參(action
);lambda函數(shù)體被調用的時候蕾总,接收者(sb
)變成了一個隱士的接收者(this
)
用變量來保存帶接收者的lambda
```
val cc : StringBuilder.() -> Unit = {
this.append("better")
this.append("wolrd")
}
fun main(args: Array<String>) {
val sb = StringBuilder("good luck ")
sb.cc()
println(sb)
}
```
lambda
與 帶接收者的lambda
要確定一個lambda是否有接收者粥航,需要看lambda被傳遞給了什么函數(shù) 函數(shù)的簽名會說明lambda是否有接收者,及其接收者的類型
我們再來一起看看 apply, with
函數(shù)
3. 在HTML構建器中使用帶接收者的lambda
用于HTML的Kotlin DSL稱為HTML構建器(類型安全構建器)
構建器在Groovy中很流行(gradle)生百,Kotlin也使用了递雀;
但gradle中,沒有提示蚀浆,因為是動態(tài)的缀程,可以瞎寫,
但Kotlin不能瞎寫市俊,編譯就報錯了杨凑,更安全了;
3.1 構建一個HTML
```
fun createSimpleTable() = createHTML().
table {
tr {
td {""}
}
}
```
以上代碼摆昧,table撩满、tr、td 都是函數(shù)绅你,是高階函數(shù)伺帘,接收帶 接收者的lambda
為參數(shù);
在傳遞給table函數(shù)的lambda中忌锯,可用tr來創(chuàng)建<tr>標簽伪嫁,而在lambda之外,tr函數(shù)無法被解析偶垮;td類似张咳;
每一個代碼塊中的命名解析上下文是由每一個lambda
的接收者的類型定義的帝洪;如:
- 傳遞給table的lambda的接收者類型是Table,其定義tr函數(shù)脚猾;
- 同理葱峡,tr 函數(shù)的lambda的接收者是Tr;
代碼
```
open class Tag
class TABLE : Tag() {
fun tr(init: TR.() -> Unit) {
TR().init()
}
}
class TR : Tag() {
fun td(init: TD.() -> Unit) {
}
}
class TD:Tag()
fun table(init: TABLE.() -> Unit) = TABLE().init()
```
調用代碼
```
table {
tr {
(this@tr).td {
}
}
}
```
完整實現(xiàn)
```
open abstract class MyTag(val name:String) {
protected val children = mutableListOf<MyTag>()
override fun toString(): String {
return "<$name>${children.joinToString("")}</$name>" // <td></td>
}
}
class MyTd:MyTag("td")
class MyTr:MyTag("tr") {
fun td(init: MyTd.() -> Unit) {
val td = MyTd()
td.init()
children.add(td)
}
}
class MyTable:MyTag("table") {
fun tr(init: MyTr.() -> Unit) {
val tr = MyTr()
tr.init()
children.add(tr)
}
}
fun table(init: MyTable.() -> Unit) : MyTable {
val table = MyTable()
table.init()
return table
}
fun main(args: Array<String>) {
val ta = table {
tr {
td { }
td { }
}
}
println(ta)
}
```