DSL的全称是领域特定语言(Domain Specific Language),它是编程语言赋予开发者的一种特殊能力,通过它我们可以编写出一些看似脱离其原始语法结构的代码,从而构建出一种专有的特殊结构。
Kotlin也是支持DSL的,并且在Kotlin中实现DSL的方式并不固定,比如infix函数构建出的特有语法结构就属于DSL。不过Kotlin中最常见的实现DSL方式是通过高阶函数。
其实DSL我们一直都在用,比如我们想要在项目中添加一些依赖库,需要在build.gradle文件中编写如下内容:
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
}
Gradle是一种基于Groovy语言的构建工具,因此上述的语法结构其实就是Groovy提供的DSL功能。
借助Kotlin的DSL,我们也可以实现类似的语法结构。
首先新建一个DSL.kt文件,然后在里面实现定义一个Dependency类,代码如下所示
class Dependency {
val libraries=ArrayList<String>()
fun implementation(lib:String){
libraries.add(lib)
}
}
这里我们使用了一个List集合来保存所有的依赖库,然后又提供了一个implementation()方法,用于向List集合中添加依赖库
接下来定义一个dependencies高阶函数,代码如下所示
fun dependencies(block:Dependency.()->Unit):List<String>{
val dependency=Dependency()
dependency.block()
return dependency.libraries
}
可以看到,dependencies函数接收一个函数类型参数,并且该参数是定义到Dependency类当中的,因此调用它的时候需要先创建一个Dependency的实例,然后再通过该实例调用函数类型参数,这样传入的Lambda表达式就能得到执行了。最后我们将Dependency类中保存的依赖库集合返回。
我们可以通过dependencies函数的返回值来获取所有添加的依赖库,代码如下所示:
fun main() {
val libraries = dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2")
}
for(lib in libraries){
println(lib)
}
}
这里用一个libraries变量接收dependencies函数的返回值,然后使用for -in循环将集合中的依赖库全部打印出来。
可以看到,已经成功将使用DSL语法结构添加的依赖库全部获取到了。
在前端开发中,网页的展示都是由浏览器解析HTML代码来实现的。HTML中定义了很多标签,其中< table>标签用于创建一个表格,< tr >标签用于创建表格的行,< td >标签用于创建单元格。将这3种标签嵌套使用,就可以定制出包含任意行列的表格了。
例如:
<table>
<tr>
<td>Apple</td>
<td>Grape</td>
<td>orange</td>
</tr>
<tr>
<td>Pear</td>
<td>Banana</td>
<td>Watermelon</td>
</tr>
</table>
这样就会创建一个两行三列的表格,修改后缀名,将文件名改为test.html然后双击文件,使用浏览器打开,显示如下:
如果有一个需求,要求我们在Kotlin中动态生成表格所对应的HTML代码,最直接的方式是字符串拼接,但是这种方式比较繁琐,而且代码难以阅读。
而借助DSL,我们可以使用一种方便的语法结构来动态生成表格所对应的HTML代码
定义一个Td类,代码如下
class Td{
var content=""
fun html()="\n\t\t<td>$content</td>"
}
由于< td >标签表示一个单元格,其中必然是要包含内容的,因此这里我们使用了一个content字段来存储单元格中显示的内容。另外,还提供了一个html()方法,当调用这个方法时就返回一段< td >标签的HTML代码,并将content中存储的内容拼接进去。为了让最终输出的结果更加直观,使用了\n和\t转义符来进行换行和缩进。
接下来定义一个Tr类,代码如下所示:
class Tr {
private val children=ArrayList<Td>()
fun td(block:Td.()->String){
val td=Td()
td.content=td.block()
children.add(td)
}
fun html():String{
val builder=StringBuilder()
builder.append("\n\t<tr>")
for(childTag in children){
builder.append(childTag.html())
}
builder.append("\n\t</tr>")
return builder.toString()
}
}
由于< tr >标签表示表格的行,它是可以包含多个< td >标签的,因此我们创建了一个children集合,用于存储当前Tr所包含的Td对象。接下来提供了一个td()函数,它接收一个定义到Td类中并且返回值是String的函数类型参数。 当调用td()函数时,会先创建一个Td对象,接着调用函数类型参数并获取它的返回值,然后赋值到Td类的content字段当中。这样就可以将调用td()函数时传入的Lambda表达式的返回值赋给content字段了。最后将创建了的td对象添加到children集合当中。
另外,Tr类中也定义了一个html()方法,它的作用和刚才Td类当中的html()方法一致。只是由于每个Tr都可能会包含很多个Td,因此我们需要使用循环来遍历children集合,将所有的子Td都拼接到< tr >标签中,从而返回一段嵌套的HTML代码。
定义好了Tr类之后,我们就可以使用如下语法格式来构建表格中的一行数据:
val tr=Tr()
tr.td{"Apple"}
tr.td{"Orange"}
tr.td{"Grape"}
接下来再定义一个Table类,代码如下
class Table {
private val children=ArrayList<Tr>()
fun tr(block:Tr.()->Unit){
val tr=Tr()
tr.block()
children.add(tr)
}
fun html():String{
val builder=StringBuilder()
builder.append("<table>")
for (childTag in children){
builder.append(childTag.html())
}
builder.append("\n</table>")
return builder.toString()
}
}
现在我们就可以使用如下语法结构来创建一个表格了:
val table=Table()
table.tr{
td{"Apple"}
td{"Orange"}
td{"Grape"}
}
table.tr{
td{"Pear"}
td{"Banana"}
td{"Watermelon"}
}
还可以再进一步简化,定义一个table()函数,代码如下
fun table(block:Table.()->Unit):String{
val table=Table()
table.block()
return table.html()
}
最后我们就可以使用如下语法结构动态生成一个表格所对应的HTML代码了:
fun main() {
val html = table {
tr {
td {
"Apple"
}
td {
"Orange"
}
td {
"Grape"
}
}
tr {
td {
"Pear"
}
td {
"Banana"
}
td {
"Watermelon"
}
}
}
println(html)
}
另外,在DSL中也可以使用Kotlin的其他语法特性,比如通过循环来批量生成< tr >和< td >标签:
fun main() {
val html = table {
repeat(2){
tr {
val fruits= listOf("Apple","Grape","Orange")
for(fruit in fruits){
td {
fruit
}
}
}
}
}
println(html)
}