Kotlin使用技巧(一)-接口與命名參數的優雅運用

拋出問題

看過我的Kotlin-高階函數的使用(二)都知道,我們的setOnClickListener可以這樣寫:

view.setOnClickListener {

}

但是當接口有多個實現方法的時候我們可能就需要這樣實現了:

edittext.addTextChangedListener(object :TextWatcher{
            override fun afterTextChanged(s: Editable?) {
            }

            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            }

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            }

        })

通常我們只需要在onTextChanged裏執行一些邏輯,其餘的方法用不到,這樣就顯得很不優雅了,那有沒有辦法顯得更加的優雅呢,答案肯定是有噠~~~

解決問題

1.使用擴展函數封裝對應的方法

我們可以使用擴展方法內部實現3個方法,然後讓其中一個方法invoke出去,實現之後像這樣:

edittext.onTextChanged { s, start, before, count ->
            
}

實現:

fun EditText.onTextChanged(changed:(s: CharSequence?, start: Int, before: Int, count: Int)->Unit){
     addTextChangedListener(object : TextWatcher{
         override fun afterTextChanged(s: Editable?) {
         }

         override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
         }

         override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
             changed(s,start,before, count)
         }

     })
}

這裏我假設你已經理解了擴展函數的使用,如果看不太懂,可以先看看我的這篇文章Kotlin-高階函數的使用(一)

代碼一下子清爽起來了,並且我可以專注於一個方法邏輯的實現,但是有一個缺點,萬一我需要在beforeTextChanged裏實現一些邏輯呢,萬一我想實現其他的方法呢,有人會說我再申明一個beforeTextChanged的擴展方法啊,然後像這樣:

 edittext.onTextChanged { s, start, before, count ->

}
edittext.beforeTextChanged{s, start, count, after->

}

這樣寫實際上是申明瞭兩個監聽,onTextChanged是會失效的,所以這種方案pass。我們可以想到在一個方法裏申明多個lambda,貌似解決了問題,於是就像這樣:

 edittext.addTextChangedListener({ s ->

        }, { s, start, count, after ->

        }, { s, start, before, count->

        })

實現:

fun EditText.addTextChangedListener(after:(s: Editable?)->Unit,before:(s: CharSequence?, start: Int, count: Int, after: Int)->Unit,
                                    changed: (s: CharSequence?, start: Int, before: Int, count: Int) -> Unit){
    addTextChangedListener(object : TextWatcher{
        override fun afterTextChanged(s: Editable?) {
            after(s)
        }

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            before(s, start, count, after)
        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            changed(s,start,before, count)
        }

    })
}

我滴媽,這啥跟啥,誰要是這麼寫估計出門會被打。其實這種思路是對的,但是可能敲出來的代碼他有自己的想法.....

2.使用命名參數和默認值解決lambda方法區分問題

我們知道上面那種實現的缺點就是方法不易區分,so,我們只需要加入一個命名參數就解決問題啦:

        edittext.addTextChangedListener(
                after = { s ->

                },
                before = { s, start, count, after ->

                },
                changed = { s, start, before, count ->

                }
        )

實現:

inline fun EditText.addTextChangedListener(crossinline after:(s: Editable?)->Unit,
                                           crossinline before:(s: CharSequence?, start: Int, count: Int, after: Int)->Unit,
                                           crossinline changed: (s: CharSequence?, start: Int, before: Int, count: Int) -> Unit){
    addTextChangedListener(object : TextWatcher{
        override fun afterTextChanged(s: Editable?) {
            after(s)
        }

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            before(s, start, count, after)
        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            changed(s,start,before, count)
        }

    })
}

使用crossinline關鍵字來規定命名參數,但必須申明方法爲inline,關於內聯,詳見官方文檔

這樣看起來就清晰多了,每個lambda對應什麼方法一目瞭然,但是,回到我們最初說的,我們有時只需要其中的一個方法的,這樣還是要申明3個方法呀,別急,加個默認值搞定:
實現:

inline fun EditText.addTextChangedListener(crossinline after:(s: Editable?)->Unit={},
                                           crossinline before:(s: CharSequence?, start: Int, count: Int, after: Int)->Unit={
                                               _, _, _, _ -> 
                                           },
                                           crossinline changed: (s: CharSequence?, start: Int, before: Int, count: Int) -> Unit={
                                               _, _, _, _ ->
                                           }){
    addTextChangedListener(object : TextWatcher{
        override fun afterTextChanged(s: Editable?) {
            after(s)
        }

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            before(s, start, count, after)
        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            changed(s,start,before, count)
        }

    })
}

使用:

        edittext.addTextChangedListener(
                changed = { s, start, before, count ->

                }
        )

這樣看起來就舒服多了,既達到了申明單個多個實現的靈活選擇,又達到了方法名的命名區分,感覺人生已經到達了巔峯有木有!!

我們知道如果方法申明瞭默認值,那麼這個參數可以不申明,我們正是利用了這個特性來達到的效果,關於這個特性也可以用到接口上,當我們有些接口不想讓它每次都實現,可以在接口後面加入一個返回值,如果沒有返回值可以返回一個Unit,像這樣:

interface Test {
    fun test1() = Unit
    fun test2()
}

這樣的話test2爲必須實現,test1就變成了選擇實現

總結:

經過addTextChangedListener的一個例子,我們學習了擴展函數,接口與命名參數的運用,在實際項目中,建議讀者們可以將常用的一些接口實現做一些封裝,具體選擇那種方式視實際情況而定,如果你不經常的使用它,那麼就不用去特意去封裝它,不然會造成方法臃腫。

關於方法與接口的封裝,還有更加高級的用法,就是DSL,這種方案在Anko框架中使用的非常頻繁,有興趣的可以去研究一下

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章