Kotlin DSL
什么是DSL
Domain Special Language
DSL是领域特定语言,与通用语言不通,他只管他的领域,如:SQL、正则表达式;
特点:
一般DSL都是非常简洁的;所以DSL,一般趋向于声明式、这样就需要解释,也就是效率有些影响;
一般的编程语言基本是命令式的,定义了确定的步骤;
一般的DSL很难与通用编程语言的宿主应用结合起来,
gradle对应的Groovy是动态编程语言;DSL是有自己的结构的,DSL方法调用由DSL结构规定;比如:SQL;
Kotlin 中dsl 的特点:
Kotlin允许构建整洁API的功能包括:扩展函数、中缀调用、lambda、简明语法和运算符重载;
- 完全静态类型,优势:类型检查,IDE提示等;
- 他是内部DSL,语法兼容;不完全独立,但保留了独立语法DSL优点;
解决DSL的一些问题;
2. 带接收者的Lambda
带接收者的Lambda是Kotlin的一大特性,可以使用一个结构来构建API,拥有结构是区分DSL与普通API的关键特征;
我们来看下面3个函数
2.1 buildString、with 和 apply 函数
buildString函数
```
val abc = buildString {
for (alpha in 'A'..'Z') {
append(alpha)
}
}
println(abc)
```
with函数
```
val s = with(sb) {
for(a in 'a'..'z') {
append(a)
}
this.toString()
}
println(s)
```
apply 函数
```
val l = StringBuilder().apply {
for(a in 'a'..'z') {
append(a)
}
}.toString()
println(l)
```
一起过一下他们的方法签名;
2.2 带接收者的lambda和扩展函数类型
来创建一个自己的 mybuildString
函数
```
fun mybuildString(action: (StringBuilder) -> Unit) : String {
val sb = StringBuilder()
action(sb)
return sb.toString()
}
fun main(args: Array) {
// 调用的使用需要it
val s = mybuildString {
it.append("Hello")
it.append("World")
}
println(s)
}
```
很好理解上面的代码,但是调用时候,需要传it
,不够简洁,我们来改一下:
需要将lambda转换成带接收者的lambda
将接收者的特殊状态赋予lambda参数的一个,这样就可以不需要任何修饰符就能直接调用他的成员;
```
// 定义带接收者的lambda的类型参数
fun mybuildString2(action: StringBuilder.() -> Unit) : String {
val sb = StringBuilder()
sb.action()
return sb.toString()
}
```
这里传递的是一个带接收者的lambda作为参数(匿名扩展函数),可以去掉lambda函数体中的 it
变化:用扩展函数类型取代了普通函数类型来声明参数;
StringBuilder.() -> Unit 替代了 (StringBuilder) -> Unit,
这个特殊的类型(StringBuilder)叫做接收者类型,传递给lambda的这个类型的值叫做接收者对象
```
接收者类型 参数类型 返回类型
String. (Int,Int) -> Unit
```
上面的是一个扩展函数类型,接收者是String;
实际上,一个扩展函数类型,描述了一个可以被当做扩展函数来调用的代码块
不是将参数传给lambda,而是像调用扩展函数那样调用lambda; 上面的的 action
不是String类的方法,他是一个函数类型的参数,但是可以调用扩展函数一样的语法调用他;
看一下图:
函数的实参(带lambda的接收者),对应于扩展函数类型的形参(action
);lambda函数体被调用的时候,接收者(sb
)变成了一个隐士的接收者(this
)
用变量来保存带接收者的lambda
```
val cc : StringBuilder.() -> Unit = {
this.append("better")
this.append("wolrd")
}
fun main(args: Array) {
val sb = StringBuilder("good luck ")
sb.cc()
println(sb)
}
```
lambda
与 带接收者的lambda
要确定一个lambda是否有接收者,需要看lambda被传递给了什么函数 函数的签名会说明lambda是否有接收者,及其接收者的类型
我们再来一起看看 apply, with
函数
3. 在HTML构建器中使用带接收者的lambda
用于HTML的Kotlin DSL称为HTML构建器(类型安全构建器)
构建器在Groovy中很流行(gradle),Kotlin也使用了;
但gradle中,没有提示,因为是动态的,可以瞎写,
但Kotlin不能瞎写,编译就报错了,更安全了;
3.1 构建一个HTML
```
fun createSimpleTable() = createHTML().
table {
tr {
td {""}
}
}
```
以上代码,table、tr、td 都是函数,是高阶函数,接收带 接收者的lambda
为参数;
在传递给table函数的lambda中,可用tr来创建
每一个代码块中的命名解析上下文是由每一个lambda
的接收者的类型定义的;如:
- 传递给table的lambda的接收者类型是Table,其定义tr函数;
- 同理,tr 函数的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 {
}
}
}
```
完整实现
```
open abstract class MyTag(val name:String) {
protected val children = mutableListOf()
override fun toString(): String {
return "<$name>${children.joinToString("")}$name>" //
}
}
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) {
val ta = table {
tr {
td { }
td { }
}
}
println(ta)
}
```