• Kotlin基础——DSL


    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    转换为带接收者的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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    具体做法是使用扩展函数类型取代普通函数类型来声明参数的类型,将函数类型签名中的一个参数移到括号前面,并用一个.分割

    (StringBuilder) -> Unit		//一个接收StringBuild参数、无返回值的函数
    StringBuilder.() -> Unit	//将(接收者对象)参数往前移
    
    • 1
    • 2

    也声明一个扩展函数类型的变量

    val appendExcl: StringBuilder.() -> Unit = { this.append("!") }
    val sb = StringBuilder("Hi")
    sb.appendExcl()
    println(sb)
    
    • 1
    • 2
    • 3
    • 4

    Kotlin标准库中的apply和with就是利用扩展函数类型

    public inline fun  T.apply(block: T.() -> Unit): T {
       	.....
        block()			//apply的接收者被当作lambda的接收者
        return this		//返回接收者
    }
    
    public inline fun  with(receiver: T, block: T.() -> R): R {
        ......
        return receiver.block()		//返回调用Lambda的结果
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    HTML构建器

    用于Html的Kotlin DSL叫做HTML构建器,其是类型安全的

    open class Tag(val name: String) {
        private val children = mutableListOf()
        protected fun  doInit(child: T, init: T.() -> Unit) {
            child.init()
            children.add(child)
        }
    
        override fun toString() = "<$name>${children.joinToString("")}"
    }
    
    fun table(init: TABLE.() -> Unit) = TABLE().apply(init)
    
    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 createTable() =
        table {
            tr {
                td {
    
                }
            }
        }
    
    • 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

    调用

    println(createTable())
    
    
    • 1
    • 2
    • 3

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    中缀调用的DSL

    对于下面的DSL

    infix fun  T.should(matcher: Matcher) = matcher.test(this)
    
    interface Matcher {
        fun test(value: T)
    }
    
    class startWith(val prefix: String) : Matcher {
        override fun test(value: String) {
            if (!value.startsWith(prefix)) {
                throw AssertionError("$value does not start with $prefix")
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    可使用中缀调用

    "kotlin" should startWith("kot")
    
    "kotlin".should(startWith("kot"))
    
    • 1
    • 2
    • 3

    还可利用包装类进一步简化,利用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")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    "kotlin" should start with ("kot")
    
    "kotlin".should(start).with("kot")
    
    • 1
    • 2
    • 3

    基本数据类型上定义扩展

    val Int.days: Period
        get() = Period.ofDays(this)
    
    val Period.ago: LocalDate
        get() = LocalDate.now() - this
    
    val Period.fromNow: LocalDate
        get() = LocalDate.now() + this
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    通过扩展函数实现获取一天前和一天后的日期

    println(1.days.ago)
    println(1.days.fromNow)
    
    • 1
    • 2
  • 相关阅读:
    玩转 CODING 自动化助手,助力高效研发!
    WPF UI样式介绍
    【光电工程实训】光通信与光纤通信 电子元器件认知 万用表测量参数 元件特性 激光传声实验 自由空间光通讯 光纤光通信 全反射
    leetcode 1624. 两个相同字符之间的最长子字符串
    Canvans:绘制饼图和玫瑰饼图
    docker镜像如何下载到本地
    Linux防火墙命令
    常见的限流算法简述
    【node进阶】在node.js中优雅的使用Socket.IO模块
    【QML】一文入门QML应用程序的性能分析
  • 原文地址:https://blog.csdn.net/qq_35258036/article/details/136251694