Kotlin实战:使用DSL构建结构化API去掉冗余的接口方法

这是该系列的第四篇,系列文章目录以下:java

  1. Kotlin基础:白话文转文言文般的Kotlin常识数据库

  2. Kotlin基础:望文生义的Kotlin集合操做编程

  3. Kotlin实战:用实战代码更深刻地理解预约义扩展函数bash

  4. Kotlin实战:使用DSL构建结构化API去掉冗余的接口方法app

  5. Kotlin基础:属性也能够是抽象的dom

  6. Kotlin进阶:动画代码太丑,用DSL动画库拯救,像说话同样写代码哟!编程语言

  7. Kotlin基础:用约定简化相亲ide

即便不须要 Java 接口中的某些方法,也必须将其implements,而后保持其为空实现,傻傻地处在那。利用 Kotlin 的 DSL 能够只实现本身感兴趣的方法。函数

(这篇将在上一篇的代码基础上新增功能,并利用自定义的 DSL 来简化代码。)post

引子

上篇中利用apply()语法来简化组合动画的构建过程,代码以下:

val span = 300
AnimatorSet().apply {
    playTogether(
            ObjectAnimator.ofPropertyValuesHolder(
                    tvTitle,
                    PropertyValuesHolder.ofFloat("alpha", 0f, 1.0f),
                    PropertyValuesHolder.ofFloat("translationY", 0f, 100f)).apply {
                interpolator = AccelerateInterpolator()
                duration = span
            },
            ObjectAnimator.ofPropertyValuesHolder(
                    ivAvatar,
                    PropertyValuesHolder.ofFloat("alpha", 1.0f, 0f),
                    PropertyValuesHolder.ofFloat("translationY", 0f,100f)).apply {
                interpolator = AccelerateInterpolator()
                duration = span
            }
    )
    start()
}
复制代码

若是动画的时间被拉长,须要在其暂停时显示 toast 提示,而且在结束时展现视图A,代码需作以下修改:

val span = 5000
AnimatorSet().apply {
    playTogether(
            ObjectAnimator.ofPropertyValuesHolder(
                    tvTitle,
                    PropertyValuesHolder.ofFloat("alpha", 0f, 1.0f),
                    PropertyValuesHolder.ofFloat("translationY", 0f, 100f)).apply {
                interpolator = AccelerateInterpolator()
                duration = span
            },
            ObjectAnimator.ofPropertyValuesHolder(
                    ivAvatar,
                    PropertyValuesHolder.ofFloat("alpha", 1.0f, 0f),
                    PropertyValuesHolder.ofFloat("translationY", 0f,100f)).apply {
                interpolator = AccelerateInterpolator()
                duration = span
            }
    )
    addPauseListener(object :Animator.AnimatorPauseListener{
        override fun onAnimationPause(animation: Animator?) {
            Toast.makeText(context,"pause",Toast.LENGTH_SHORT).show()
        }

        override fun onAnimationResume(animation: Animator?) {
        }

    })
    addListener(object : Animator.AnimatorListener{
        override fun onAnimationRepeat(animation: Animator?) {
        }

        override fun onAnimationEnd(animation: Animator?) {
            showA()
        }

        override fun onAnimationCancel(animation: Animator?) {
        }

        override fun onAnimationStart(animation: Animator?) {
        }
    })
    start()
}
复制代码

这一段apply()有点过长了,严重下降了它的可读性。罪魁祸首是 java 接口。虽然只用到接口中的一个方法,但却必须将其他的方法保留空实现。

有没有什么办法只实现想要的方法,去掉不用的方法?

利用 kotlin 的自定义 DSL 就能够实现。

DSL

DSL = domain specific language,即“特定领域语言”,与它对应的一个概念叫“通用编程语言”,通用编程语言有一系列完善的能力来解决几乎全部能被计算机解决的问题,像 Java 就属于这种类型。而特定领域语言只专一于特定的任务,好比 SQL 只专一于操纵数据库,HTML 只专一于表述超文本。

既然通用编程语言可以解决全部的问题,那为啥还须要特定领域语言?由于它能够使用比通用编程语言中等价代码更紧凑的语法来表达特定领域的操做。好比当执行一条 SQL 语句时,不须要从声明一个类及其方法开始。

更紧凑的语法意味着更简洁的 API。应用程序中每一个类都提供了其余类与之交互的可能性,确保这些交互易于理解并能够简洁地表达,对于软件的可维护性相当重要。

DSL 有一个普通API不具有特征:DSL 具备结构。而带接收者的lambda使得构建结构化的 API 变得容易。

带接收者的 lambda

它是一种特殊的 lambda,是 kotlin 中特有的。能够把它理解成“为接收者声明的一个匿名扩展函数”。(扩展函数是一种在类体外为类添加功能的特性)

带接收者的lambda的函数体除了能访问其所在类的成员外,还能访问接收者的全部非私有成员,这个特性是它可以轻松地构建结构。

当带接收者的 lambda 配合高阶函数时,构建结构化的 API 就变得易如反掌。

高阶函数

它是一种特殊的函数,它的参数或者返回值是另外一个函数。

好比集合的扩展函数filter()就是一个高阶函数:

//'filter的参数是一个带接收的lambda'
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}
复制代码

能够使用它来过滤集合中的元素:

students.filter { age > 18 }
复制代码

这样就是一种结构化 API 的调用(在 java 中看不到),虽然这种结构得益于 kotlin 的一个约定(若是函数只有一个参数且它是 lambda,则能够省略函数参数列表的括号)。但更关键的是 lambda 的内部,得益于带接收者的lambdaage > 18运行在一个和其调用方不一样的上下文中,在这个上下文中,能够轻松的访问到Student的成员Student.age( 指向 age 时能够省略 this )

让咱们使用这样的技巧来解决“必须实现java全部接口”的问题。

构建 DSL 解决 java 接口问题

  1. 新建类用于存放接口中各个方法的实现
class AnimatorListenerImpl {
    var onRepeat: ((Animator) -> Unit)? = null
    var onEnd: ((Animator) -> Unit)? = null
    var onCancel: ((Animator) -> Unit)? = null
    var onStart: ((Animator) -> Unit)? = null
}
复制代码

它包含四个成员,每一个成员的类型都是函数类型。看一下Animator.AnimatorListener的定义就能理解AnimatorListenerImpl的用意:

public static interface AnimatorListener {
    void onAnimationStart(Animator animation);
    void onAnimationEnd(Animator animation);
    void onAnimationCancel(Animator animation);
    void onAnimationRepeat(Animator animation);
}
复制代码

该接口中的每一个方法都接收一个Animator参数并返回空值,用 lambda 能够表达成 (Animator) -> Unit。因此AnimatorListenerImpl将接口中的四个方法的实现都保存在函数变量中,而且实现是可空的。

  1. 为 Animator 定义一个高阶扩展函数
fun AnimatorSet.addListener(action: AnimatorListenerImpl.() -> Unit) {
    AnimatorListenerImpl().apply { action }.let { builder ->
        //'将回调实现委托给AnimatorListenerImpl的函数类型变量'
        addListener(object : Animator.AnimatorListener {
            override fun onAnimationRepeat(animation: Animator?) {
                animation?.let { builder.onRepeat?.invoke(animation) }
            }

            override fun onAnimationEnd(animation: Animator?) {
                animation?.let { builder.onEnd?.invoke(animation) }
            }

            override fun onAnimationCancel(animation: Animator?) {
                animation?.let { builder.onCancel?.invoke(animation) }
            }

            override fun onAnimationStart(animation: Animator?) {
                animation?.let { builder.onStart?.invoke(animation) }
            }
        })
    }
}
复制代码

Animator定义了扩展函数addListener(),该函数接收一个带接收者的lambdaaction

扩展函数体中构建了AnimatorListenerImpl实例并紧接着应用了action,最后为Animator设置动画监听器并将回调的实现委托给AnimatorListenerImpl中的函数类型变量。

  1. 使用自定义的 DSL 将本文开头的代码改写:
val span = 5000
AnimatorSet().apply {
    playTogether(
            ObjectAnimator.ofPropertyValuesHolder(
                    tvTitle,
                    PropertyValuesHolder.ofFloat("alpha", 0f, 1.0f),
                    PropertyValuesHolder.ofFloat("translationY", 0f, 100f)).apply {
                interpolator = AccelerateInterpolator()
                duration = span
            },
            ObjectAnimator.ofPropertyValuesHolder(
                    ivAvatar,
                    PropertyValuesHolder.ofFloat("alpha", 1.0f, 0f),
                    PropertyValuesHolder.ofFloat("translationY", 0f,100f)).apply {
                interpolator = AccelerateInterpolator()
                duration = span
            }
    )
    addPauseListener{
        onPause = { Toast.makeText(context,"pause",Toast.LENGTH_SHORT).show() }
    }
    addListener { 
        onEnd = { showA() } 
    }
    start()
}
复制代码

(省略了扩展函数addPauseListener()的定义,它和addListener()是相似的。)

得益于带接收者的lambda,能够轻松地为AnimatorListenerImpl的成员onEnd赋值,这段逻辑会在动画结束时被调用。

这段调用拥有本身独特的结构,它解决了“必须实现所有 java 接口”这个特定的问题,因此它能够称得上是一个自定义 DSL 。(固然和 SQL 相比,它显得太简单了)。

下一篇会进一步使用 DSL 的思想将 Android 整套构建动画的接口重构成结构化的代码。到时候就能够使用这样的代码来构建动画:

animSet {
    objectAnim {
        target = textView
        scaleX = floatArrayOf(1.0f,1.3f)
        scaleY = scaleX
        duration = 300L
        interpolator = LinearInterpolator()
    } with objectAnim {
        target = button
        translationX = floatArrayOf(0f,100f)
        duration = 300
        interpolator = LinearInterpolator()
    } before anim{
        values = intArrayOf(ivRight,screenWidth)
        action = { value -> imageView.right = value as Int }
        duration = 400
        interpolator = LinearInterpolator()
    }
    onEnd = Toast.makeText(activity,"animation end",Toast.LENGTH_SHORT).show()
    start()
}
复制代码
相关文章
相关标签/搜索