Kotlin 之 DSL 篇二

作者: zhaoyubetter | 来源:发表于2018-01-30 22:35 被阅读24次

    参考:

    1. kotlin 实战

    1. 使用 invoke 约定构建更灵活的代码块嵌套

    invoke 约定允许把自定义类型的对象当做函数一样调用;比如:函数类对象;kotlin中,使用invoke约定也可以支持自己的对象;但此功能比较费解;

    1.1 使用 invoke约定:类似函数一样的调用的对象

    之前,我们提到过操作符重载,如:[];对应get set 方法

    类如果定义了使用operator的 invoke方法,就可以被当做函数一样调用;

    class Greeter(val greeting: String) {
        operator fun invoke(name: String) {  // operator invoke
            println("$greeting , $name")
        }
    }
    
    fun main(args: Array<String>) {
        // 允许将test实例当做函数调用
        val test = Getter("Good Lock ")
        println(test("better"))
    }
    

    1.2 invoke 约定和函数式类型

    回顾:
    lambda 除去内联的,都是被编译成实现了函数式接口(FunctionN)的类型,这些接口具有对象数量参数的invoke方法;

    当lambda作为函数调用时,这种操作被转换成一次invoke方法调用;将复杂的lambda拆成多个方法,仍然允许将他与接收函数类型参数的函数一起使用;

    如下例子(使用复杂的条件来过滤):

    data class Issue(
            val id: String, val project: String, val type: String,
            val priority: String, val description: String
    )
    
    class ImportantIssuesPredicate(val project: String) : (Issue) -> Boolean {
        // 拆分多个方法
        override fun invoke(issue: Issue): Boolean {
            return issue.project == project && issue.isImportant()
        }
        private fun Issue.isImportant(): Boolean {
            return type == "Bug" && (priority == "Major" || priority == "Critical")
        }
    }
    
    fun main(args: Array<String>) {
        val i1 = Issue("Android-1234", "Android", "Bug",  "Major", "Login fail")
        val i2 = Issue("iOS-1234", "iOS", "Bug",  "Major", "Login fail")
        val p = ImportantIssuesPredicate("Android")
    
        for(i in listOf(i1,i2).filter(p)) {  // p invoke
            println(i.id)
        }
    }
    

    如果要在一个lambda中,放入逻辑判断,就比较复杂,这里拆分了多个方法,使各个方法职责明确;
    将lambda转换成一个实现了函数类型接口的类,并重写接口的invoke方法

    直接使用lambda 如下:

    for(i in listOf(i1,i2).filter { it.project == "Android" && it.type == "Bug"
           && (it.priority == "Major" || it.priority == "Critical")
        }) {
            println(i.id)
        }
    

    1.3 invoke 约定:在Gradle中声明依赖

    如下代码:

    dependencies.compile("junit:junit:4.11")
    dependencies {
        compile("junit:junit:4.11")
    }
    

    2种方式添加依赖,实际上,第二种方式,调用的是 invoke方法,然后接受一个 lambda的参数;

    class DenpenderyHandler {
        fun compile(coordinate: String) {
            println("add dependency on $coordinate")
        }
        operator fun invoke(body: DenpenderyHandler.() -> Unit) {
            body()
        }
    }
    
    fun main(args: Array<String>) {
        val dep = DenpenderyHandler()
        dep.compile("com.git.basenet:basenet:0.0.7")
        dep {
            compile("com.git.basenet:basenet:0.0.7")
        }
    }
    

    2. 实战中的 kotlin dsl

    kotlin相关dsl的知识点,全部介绍完毕,带接收者的lambda(在我们的分享上,有一个哥们提出 扩展匿名函数)为dsl中的重点;

    那么DSL能够干什么呢?后续我们再实战中,一一来过,典型的包括:测试、Android UI 构建(anko库)

    2. 1 配合中缀调用

    实现 "kotlin" should startWith("ko")

    interface Matcher<T> {
        fun test(value: T)
    }
    // 注意 startWith 这里是个类,而不是方法
    class startWith(val prefix: String) : Matcher<String> {
        override fun test(value: String) {
            if(!value.startsWith(prefix)) {
                throw AssertionError("String $value does not start with $prefix")
            }
        }
    }
    // 中缀
    infix fun <T> T.should(matcher: Matcher<T>) = matcher.test(this)
    

    实现 "kotlin" should start with "ko"

    // 一个单利,DSL文法的一部分,用来过渡方法调用而已,没其他作用
    object start
    // 定义with中缀函数
    class StartWrapper(val value: String) {
        infix fun with(prefix: String) {
            if (!value.startsWith(prefix)) {
                throw AssertionError("String $value does not start with $prefix")
            }
        }
    }
    infix fun String.should(x: start): StartWrapper = StartWrapper(this)
    

    注意:上面的start类,是DSL文法的一部分;中缀调用和object实例的结合能够为DSL构建相当复杂的文法,用起来很清晰,写起来,感觉挺费解的
    我们也可以实现 end with 啥的,好吧,来写一个;

    2. 2 基本数据类型定义扩展:处理日期

    实现:1.days.ago,需要Java 8

    val Int.days : Period
        get() = Period.ofDays(this)         // this 表示数字字面值
    
    val Period.ago: LocalDate
        get() = LocalDate.now() - this      // 运算符重载 LocalDate.minus
    
    val Period.fromNow: LocalDate
        get() = LocalDate.now() + this      // 运算符重载 LocalDate.plus
    

    对应github: https://github.com/yole/kxdate

    2. 3 Anko: 动态创建Android UI

    3. 后续安排

    整个Kotlin的基础分享,除了 协程外,都完毕了;
    后续我们从框架开始入手:

    1. 当然是 Android Anko
    2. json : JKid、Klaxon
    3. http client: Fuel
    4. 响应式: rxKotlin;
    5. web : Kara, Ktor
    6. 桌面: JavaFx

    相关文章

      网友评论

        本文标题:Kotlin 之 DSL 篇二

        本文链接:https://www.haomeiwen.com/subject/rvfpaxtx.html