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()
}
複製代碼
相關文章
相關標籤/搜索