Kotlin 作为有高级特性的语言,凭借高阶函数也能支持实现 DSL 。
知识储备
函数
Kotlin 中,函数是“一等公民”。
定义一个函数 sum
:
1 2 3
| fun sum(a: Int, b: Int): Int { return a + b }
|
函数类型
Kotlin 中,函数是有类型的。上面 sum
函数的类型是 (Int, Int) -> Int
高阶函数
函数参数中有函数,或者返回函数的函数,就是高阶函数。
这里定义一个函数 transform
,用于对 2 个 Int 值进行转换操作:
1 2 3
| fun transform(a: Int, b: Int, trans: (Int, Int) -> Int): Int { return trans(a, b) }
|
函数引用
既然函数是有类型的,那函数本身就是一个函数对象(参考类与对象的关系),我们也可以通过引用的方式使用函数:
1 2
| val func = ::sum func(1, 2)
|
上面定义的高阶函数 transform
,便可以使用函数引用的方式来进行函数的调用:
匿名函数
Kotlin 在缺省函数名的情况下定义出来的函数就是匿名函数。
对于上述的 sum
函数,其匿名函数形态:
1 2 3
| val func = fun(a: Int, b: Int): Int { return a + b }
|
对于上述的 transform
函数,便可以使用匿名函数的方式来调用:
1 2 3
| transform(1, 2, fun(a: Int, b: Int): Int { return a + b })
|
Lambda语法糖
匿名函数的表达方式较为繁琐,可以使用 Lambda 演算
来对齐进行简化,简化后的表达式就是 Lambda 表达式
。
上面的匿名函数的 Lambda 表达式形态:
1
| val func : (Int, Int) -> Int = {a: Int, b: Int -> a + b}
|
因为 Kotlin 支持类型推导,可以简化:
1 2 3
| val func = {a: Int, b: Int -> a + b}
val func2: (Int, Int) -> Int = {a, b -> a + b}
|
对于前述 transform
函数,便可以使用 Lambda 表达式的方式调用:
1 2 3
| transform(1, 2, {a, b -> a + b })
|
如果高阶函数最后一个参数是函数,可以将 Lambda 表达式移至函数外:
1 2 3
| transform(1, 2) { a, b -> a + b }
|
扩展函数
在不修改已有类的前提下,对其新增方法,可以通过扩展函数来实现。例如:
1 2 3
| fun View.isVisible(): Boolean { return this.visibility == View.VISIBLE }
|
上面的 View
是 接收者(Receiver)类型。
扩展函数也是函数,也有其函数类型:
1
| val visibleFunc: (View) -> Boolean = View::isVisible
|
可以看出,扩展函数函数类型的第一个参数就是 Receiver 类型。
运算符重载
Java 中没有运算符重载,Kotlin 能进行运算符重载。重载运算符能通过扩展函数或者成员函数来实现。
看一个扩展函数实现的案例:
1 2 3 4 5 6 7 8 9
| data class Point(val x: Int, val y: Int)
operator fun Point.unaryMinus() = Point(-x, -y)
fun main() { val point = Point(1, 2) print(-point) }
|
invoke
运算符也可以重载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class ContentParser { fun parse(file: File): String { }
operator fun invoke(file: File) = parse(file) }
fun main() { val parser = ContentParser() val file = File("/path/to/file") parser.invoke(file) parser(file) }
|
HTML DSL 设计实现
对于如下 HTML
代码:
1 2 3 4 5 6 7 8 9 10
| <html> <head> <meta charset="utf-8"></meta> </head> <body> <div style="width: 200px; height: 200px; line-height: 200px; background-color: #FF4081; text-align: center;"> <span style="color: white; font-family: Microsoft YaHei;">Hello Kotlin DSL</span> </div> </body> </html>
|
可以看到 HTML 本身是非常结构化的,HTML 由标签组成,每个标签可以有属性(属性 key, 属性 value )和值,这里的值包括 子标签 和 文本。
我们的目标是用 DSL 化的语言来描述上述 HTML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| val htmlContent = "html" { "head" { "meta" { "charset"("utf-8") } }
"body" { "div" { "style"("width: 200px; height: 200px; line-height: 200px; background-color: #FF4081; text-align: center;") "span" { "style"("color: white; font-family: Microsoft YaHei;") +"Hello Kotlin DSL" } } } } .render()
|
分析下上面的 DSL:
原型应该是: fun String.invoke(function: FunctionType)
,用来表示一个 HTML 节点
原型应该是: operator fun String.invoke(propertyValue: String)
,用来表示一个 HTML 属性的 key 和 value
原型应该是: operator fun String.unaryPlus()
,用来表示 HTML 标签的文本。
以上便是此 HTML DSL 的要点。
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| import java.io.File
interface Node { fun render(): String }
class StringNode(private val content: String) : Node { override fun render(): String { return content }
override fun toString() = render() }
class TagNode(private val tagName: String) : Node {
private val elements = ArrayList<Node>()
private val properties = HashMap<String, Any>()
override fun render(): String { return """<$tagName${ properties.takeIf { it.size > 0 }?.map { "${it.key}=\"${it.value}\"" }?.joinToString(" ", " ") ?: "" }>${elements.joinToString("") { it.render() }}</$tagName>""".trimIndent() }
operator fun String.invoke(block: TagNode.() -> Unit): TagNode { val tagNode = TagNode(this) tagNode.block() this@TagNode.elements += tagNode return tagNode }
operator fun String.invoke(propertyValue: Any) { this@TagNode.properties[this] = propertyValue }
operator fun String.unaryPlus() { this@TagNode.elements += StringNode(this) }
override fun toString() = render()
}
operator fun String.invoke(block: TagNode.() -> Unit): Node { val tagNode = TagNode(this) tagNode.block() return tagNode }
fun main() { val htmlContent = "html" { "head" { "meta" { "charset"("utf-8") } }
"body" { "div" { "style"("width: 200px; height: 200px; line-height: 200px; background-color: #FF4081; text-align: center;") "span" { "style"("color: white; font-family: Microsoft YaHei;") +"Hello Kotlin DSL" } } } } .render()
println(htmlContent)
File("HelloKotlinDSL.html").writeText(htmlContent)
val bodyContent = "body" { "h1" { +"Hello Kotlin" } } println(bodyContent)
val relativeLayout = "RelativeLayout" { "android:layout_width"("match_parent") "android:layout_height"("wrap_content")
"TextView" { "android:id"("@+id/textview") "android:layout_width"("200dp") "android:height"("wrap_content") "android:text"("Hello Kotlin DSL") } } println(relativeLayout) }
|
参考