DSL(领域特定语言)
常见的DSL就是SQL和正则表达式,用于操作数据库和文本字符串,Kotlin DSL通常为嵌套的Lambda表达式或链式方法,如
- https://github.com/gradle/gradle-script-kotlin 用于构建Gradle脚本
- https://github.com/JetBrains/Exposed 用于操作数据库
- https://github.com/Kotlin/kotlinx.html 用于生成HTML
带接收者的Lambda和扩展函数类型
对于普通的生成字符串函数,需要在Lambda中使用it指向StringBuilder实例
fun buildString(builderAction: (StringBuilder) -> Unit): String {
val sb = StringBuilder()
builderAction(sb)
return sb.toString()
}
val s = buildString {
it.append("Hello ")
it.append("World")
}
println(s)
转换为带接收者的Lambda可通过this或直接调用方法
fun buildString(builderAction: StringBuilder.() -> Unit): String {
val sb = StringBuilder()
sb.builderAction()
return sb.toString()
}
val s = buildString {
this.append("Hello ")
append("World")
}
println(s)
具体做法是使用扩展函数类型取代普通函数类型来声明参数的类型,将函数类型签名中的一个参数移到括号前面,并用一个.分割
(StringBuilder) -> Unit //一个接收StringBuild参数、无返回值的函数
StringBuilder.() -> Unit //将(接收者对象)参数往前移
也声明一个扩展函数类型的变量
val appendExcl: StringBuilder.() -> Unit = { this.append("!") }
val sb = StringBuilder("Hi")
sb.appendExcl()
println(sb)
Kotlin标准库中的apply和with就是利用扩展函数类型
public inline fun <T> T.apply(block: T.() -> Unit): T {
.....
block() //apply的接收者被当作lambda的接收者
return this //返回接收者
}
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
......
return receiver.block() //返回调用Lambda的结果
}
HTML构建器
用于Html的Kotlin DSL叫做HTML构建器,其是类型安全的
open class Tag(val name: String) {
private val children = mutableListOf<Tag>()
protected fun <T : Tag> doInit(child: T, init: T.() -> Unit) {
child.init()
children.add(child)
}
override fun toString() = "<$name>${children.joinToString("")}</$name>"
}
class TABLE : Tag("table") {
fun tr(init: TR.() -> Unit) = doInit(TR(), init)
}
class TR : Tag("tr") {
fun td(init: TD.() -> Unit) = doInit(TD(), init)
}
class TD : Tag("td")
fun table(init: (TABLE) -> Unit) = TABLE().apply(init)
fun createTable() =
table {
tr {
td {
}
}
}
调用
- 对于table(),创建Lambda,Lambda里面创建TABLE类并调用其tr()函数
- 对于tr(),创建Lambda,Lambda里面创建创建TR类并调用其td()函数
- 对于td(),创建Lambda,Lambda里面创建创建TD类
println(createTable())
<table><tr><td></td></tr></table>
下面代码可能会更清晰一点
open class Tag(val name: String) {
private val children = mutableListOf<Tag>()
protected fun <T : Tag> doInit(child: T, init: T.() -> Unit) {
child.init()
children.add(child)
}
override fun toString() = "<$name>${children.joinToString("")}</$name>"
}
class TABLE : Tag("table") {
fun tr(init: (TR) -> Unit) = doInit(TR(), init)
}
class TR : Tag("tr") {
fun td(init: (TD) -> Unit) = doInit(TD(), init)
}
class TD : Tag("td")
fun table(init: (TABLE) -> Unit) = TABLE().apply(init)
fun createTable() =
table {
it.tr {
it.td {
}
}
}
fun createTable2() =
table {
table: TABLE -> table.tr {
tr: TR -> tr.td {
td: TD ->
}
}
}
invoke约定
重写invoke()可以让对象像函数一样调用,p(1)会被编译成p.invoke(1)
class Person(val name: String) {
operator fun invoke(age: Int) {
println("$name,$age")
}
}
val p = Person("A")
p(1)
Gradle中的DSL
class DependencyHandler {
fun compile(coordinate: String) {
println("add dependency on $coordinate")
}
operator fun invoke(body: DependencyHandler.() -> Unit) {
body()
}
}
val dependencies = DependencyHandler()
dependencies.compile("com.demo.demo-lib:1.0.0")
dependencies {
compile("com.demo.demo-lib:1.0.0")
}
中缀调用的DSL
对于下面的DSL
infix fun <T> T.should(matcher: Matcher<T>) = matcher.test(this)
interface Matcher<T> {
fun test(value: T)
}
class startWith(val prefix: String) : Matcher<String> {
override fun test(value: String) {
if (!value.startsWith(prefix)) {
throw AssertionError("$value does not start with $prefix")
}
}
可使用中缀调用
"kotlin" should startWith("kot")
"kotlin".should(startWith("kot"))
还可利用包装类进一步简化,利用obetject对象选择不同类型的should()重载方法
object start
infix fun String.should(x: start): StartWrapper = StartWrapper(this)
class StartWrapper(val value: String) {
infix fun with(prefix: String) =
if (!value.startsWith(prefix))
throw AssertionError("$value does not start with $prefix")
else
println("success")
}
"kotlin" should start with ("kot")
"kotlin".should(start).with("kot")
基本数据类型上定义扩展
val Int.days: Period
get() = Period.ofDays(this)
val Period.ago: LocalDate
get() = LocalDate.now() - this
val Period.fromNow: LocalDate
get() = LocalDate.now() + this
通过扩展函数实现获取一天前和一天后的日期
println(1.days.ago)
println(1.days.fromNow)