Kotlin:函数DSL实战

DSL(domain specific language),即领域专用语言:专门解决某一特定问题的计算机语言,比如大家耳熟能详的 SQL 和正则表达式。
Kotlin DSL的简单定义:“使用 Kotlin 语言开发的,解决特定领域问题,具备独特代码结构的API 。”

扩展函数/属性

假设我们想获取昨天的日期,昨天相对于今天来说也就是一天前,那么获取这个方法的DSL写法就应该是:

 val yesterday = 1.days.ago 

由上面的结构,我们可以设计扩展函数如下:

fun Int.days() = Period.ofDays(this)
fun Period.ago() = LocalDate.now() - this

//调用
val yesterday = 1.days().ago()

我们只需要修改扩展函数为扩展属性,即可实现最终效果,如下:

val Int.days: Period
    get() = Period.ofDays(this)

val Period.ago: LocalDate
    get() = LocalDate.now() - this

//调用
val yesterday = 1.days.ago

因此Kotlin DSL有一个显著的特征:近似于口语(个人理解)。

定义一个类及接口

我们先定义一个包含多个方法接口,和需要使用该接口的类。一步步来展示在Kotlin如何使用DSL。

interface Listener {
    fun onStart()

    fun onNext()

    fun onComplete(result:String)
}

class Work {
    private var listener: Listener? = null
    fun start() {
        listener?.run {
            onStart()
            onNext()
            onComplete("onComplete")
        }
    }

    fun addListener(listener: Listener) {
        //接口赋值
        this.listener = listener
    }
    //doSomething
}doSomething
}

基本使用

如下代码,便是这个接口最基本的使用。

    val  work = Work()
    work.addListener(object :Listener{
        override fun onStart() {

        }

        override fun onNext() {

        }

        override fun onComplete(result:String) {

        }
    })

思考:日常开发中接口中的每一个方法都不一定必须会使用。如上频繁使用该接口,但是我们很多时候只关心onComplete方法中的回调值,所以每次都书写onStart,onNext会造成很多的代码冗余。怎么去解决这个问题呢?

继承

在Java开发中,我们通常会使用继承写一个中间类,来避免接口方法过多的情况。如下:

open class AListener : Listener {
    override fun onStart() {

    }

    override fun onNext() {

    }

    override fun onComplete(result: String) {

    }
}

这时我们需要哪个方法,重写哪个方法即可:

  work.addListener(object : AListener() {
        override fun onComplete(result: String) {
            super.onComplete(result)
        }
    })

高阶函数

学习了Kotlin之后,我们发现函数可以作为另一个函数的参数(此时这个函数叫高阶函数),我们可以把上面接口方法的作为函数参数传给扩展函数,简化调用:

//别名
typealias onEmpty = () -> Unit
typealias onResult = (String) -> Unit

inline fun Work.addListenerHigh(
        crossinline onStart: onEmpty = {},
        crossinline onNext: onEmpty = {},
        crossinline onComplete: onResult = { result -> }) {
    this.addListener(object : Listener {
        override fun onStart() {
            onStart.invoke()
        }

        override fun onNext() {
            onNext.invoke()
        }

        override fun onComplete(result: String) {
            onComplete.invoke(result)
        }
    })
}

这时候我们只需要调用扩展函数,传入自己关注的接口方法即可。

 work.addListenerHigh(onComplete = { result: String -> {} })

改造后的高阶函数,已经非常接近DSL结构,其实这里只是利用了Kotlin的具名参数的特性,并不是真正意义上的DSL结构。
接下来我们再进一步利用高阶含函数的特性将其改造成DSL结构。

DSL

我们先定义一个类,这个类的属性值都是上面接口对应的函数参数。

//别名
typealias onEmpty = () -> Unit
typealias onResult = (String) -> Unit

class ListenerDSL {
    var _onStart: onEmpty? = null
    var _onNext: onEmpty? = null
    var _onComplete: onResult? = null


    fun onStart(onEmpty: onEmpty) {
        _onStart = onEmpty
    }

    fun onNext(onEmpty: onEmpty) {
        _onNext = onEmpty
    }

    fun onComplete(onNext: onResult) {
        _onComplete = onNext
    }
}

然后使用扩展函数的编写DSL:

fun Work.addListenerDSL(init: ListenerDSL.() -> Unit) {
    val listenerDSL = ListenerDSL()
    listenerDSL.init()
    this.addListener(object : Listener {
        override fun onStart() {
            listenerDSL._onStart?.invoke()
        }

        override fun onNext() {
            listenerDSL._onNext?.invoke()
        }

        override fun onComplete(result: String) {
            listenerDSL._onComplete?.invoke(result)
        }
    })
}

这个时候我们只需要调用:

   val work = Work()
    work.addListenerDSL {
        onStart { println("onStart") }
        onComplete { result -> println(result) }
    }
    work.start()

这个时候的写法就很赏心悦目了。

参考

https://www.jianshu.com/p/f5f0d38e3e44
https://www.jianshu.com/p/5b8c4cc38814

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值