28

Kotlin使用技巧之接口与命名参数的优雅运用 - 简书

 4 years ago
source link: https://www.jianshu.com/p/ac752487fd71?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Kotlin使用技巧之接口与命名参数的优雅运用

0.8612019.01.30 11:30:22字数 967阅读 924

看过我的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框架中使用的非常频繁,有兴趣的可以去研究一下


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK