Kotlin-高阶函数进阶之路

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情

前言

很多同学刚接触Kotlin,或者一直以Java的方式写Kotlin,对Kotlin中的 高阶函数,扩展函数,高阶扩展函数,SamType, DSL 的一些写法不熟悉。这里以一个例子来说明一下如何进阶演变。

一、扩展函数

Kotlin中的扩展函数特性让我们的代码变得更加简单和整洁,它可以减少很多的样板代码,大大提高开发的效率。

我们常用的就是定义在顶层的扩展函数,如:

fun Any.toast(msg: String?) {
    ToastUtils.makeText(CommUtils.getContext(), msg)
}

虽然用的最多的都是顶层的扩展函数,但是扩展函数还是能限定作用域

fun String.foo() {
    YYLogUtils.w("扩展函数 foo1")
}

class TestNet {

    fun String.foo() {
        YYLogUtils.w("扩展函数 foo2")
    }
}

那么在类部使用和类外使用调用的是不同的方法。

除了扩展函数,我们还能扩展类的属性:

val TestNet.no: Int get() = 1  //扩展成员变量

class TestNet {}

使用的时候,可以打印对象的属性了:

   val testNet = TestNet()
YYLogUtils.w("no:" + testNet.no)

扩展函数相信大家都很熟悉,我就简单的介绍一下,下面看看高阶函数。

二、高阶函数

听起来挺唬人,其实就可以理解为一个参数类型,之前Java的参数都是基本类和类型,不能把函数当参数,最多就是使用接口当参数。

Kotlin支持把函数当做参数类型,看看如何定义:

扫描二维码关注公众号,回复: 14311399 查看本文章
fun requestNetwork(
        onCancel: () -> Unit,   //空参和空返回值
        onFinished: ((Boolean) -> Boolean)? = null,  //带参数带返回值
        onFailed: (FailedCallback) -> Unit     //使用带参数的高阶函数的方式
    )

高阶函数在Kotlin中可以说是代替了接口的回调

    val callback: (Int, String) -> Unit = { int, str ->
        toast("int : $int ; str: $str")
    }

    //启动Fragment
    start(Demo11OneFragment2(callback))

另一边就可以通过这个callback回调给当前页面

class Demo11OneFragment2(private val _callback: ((Int, String) -> Unit)?) : BaseFragment() {

    ...

    override fun onDestroy() {
        super.onDestroy()
       //高阶函数回调
        _callback?.invoke(10, "返回给One Page的数据")
    }
}

下面我们通过一个例子来说明高阶函数与扩展函数的结合:

class TestNet {

    fun requestNetwork(
        onCancel: () -> Unit,   //空参和空返回值
        onLoading: ((isShowLoading: Boolean) -> Boolean)? = null,  //带参数带返回值
        onSuccess: SuccessCallback,
        onFailed: FailedCallback
    ) {

        MainScope().launch {
            onLoading?.invoke(true)

            val result = withContext(Dispatchers.IO) {
                delay(1500)

                return@withContext Random(10).nextLong()
            }

            when {
                result > 8 -> {
                    onCancel()
                }
                result > 5 -> {
                    onSuccess.onSuccess()
                }
                else -> {
                    onFailed.onFailed()
                }
            }
        }

    }


    interface SuccessCallback {
        fun onSuccess()
    }


    interface FailedCallback {
        fun onFailed()
    }
}

很简单的例子,模拟网络请求,通过不同的值回调不同的函数。

看看如何接收回调:

        TestNet().requestNetwork(onCancel = {

            },onFailed = object : TestNet.FailedCallback {
                override fun onFailed() {

                }
            }, onSuccess = object : TestNet.SuccessCallback {
                override fun onSuccess() {

                }
            })

这就有点不优雅了,我们的原则是能去掉object:xx 这种函数的尽量去掉。其实这种 interface 接口的方式 ,不能使用高阶函数那样直接{ } 回调,不过我们可以通过SamType的方式来优化。

三、SamType

Java8 可以在只有单一抽象方法的接口称为SAM接口或函数式接口,通过Lambda可以大大简化SAM接口的调用。

Kotlin默认是不行的,但是1.4版本之后Kotln开始支持了,看看如何使用。

    interface NetworkTrackCallback {
        fun onCallEnd(map: Map<String, Any>)
    }

可以看到使用报错的

改为Sam的方式,直接加fun

    fun interface NetworkTrackCallback {
        fun onCallEnd(map: Map<String, Any>)
    }

这样就可以直接使用了:

我们修改上面请求网络的例子:

    fun interface SuccessCallback {
        fun onSuccess()
    }


    fun interface FailedCallback {
        fun onFailed()
    }

定义的时候就能直接使用{}了。

     TestNet().requestNetwork(onCancel = {

            },onFailed = {
                toast("test network onFailed")
            }, onSuccess = {
                toast("test network onSuccess")
            })

四、高阶扩展函数

顾名思义,就是高阶函数+扩展函数的使用。之前的例子都是 onSuccess onFailed 直接回调的接口对象,我们可以通过高阶函数或高阶扩展函数的的方式定义。

直接上代码:

class TestNet {

    fun requestNetwork(
        onCancel: () -> Unit,   //空参和空返回值
        onLoading: ((isShowLoading: Boolean) -> Boolean)? = null,  //带参数带返回值
        onSuccess: SuccessCallback.() -> Unit,  //使用高阶扩展函数的方式
        onFailed: (FailedCallback) -> Unit  //使用带参数的高阶函数的方式
    ) {

        MainScope().launch {
            onLoading?.invoke(true)

            val result = withContext(Dispatchers.IO) {
                delay(1500)

                return@withContext Random().nextInt(10)
            }

            YYLogUtils.w("result:$result")

            when {
                result > 8 -> {
                    onCancel()
                }
                result > 5 -> {
                    onSuccess(SuccessCallback {
                        YYLogUtils.w("高阶扩展函数调用执行")
                    })
                }
                else -> {
                    onFailed(FailedCallback {
                        YYLogUtils.w("可以随便写点什么逻辑")
                    })
                }
            }
        }

    }

    fun interface SuccessCallback {
        fun onSuccess()
    }


    fun interface FailedCallback {
        fun onFailed()
    }
}

上面的注释已经标明,使用 SuccessCallback.() -> Unit 为高阶扩展函数。使用 (FailedCallback) -> Unit 为高阶函数。可以实现一样的效果,只是使用回调的时候,一个是it 一个是this。

并且使用高阶函数作为参数的话,就不是一个纯粹的回调了,而是二重回调。你中有我,我中有你,外面一层是我回调你,内部的函数是你回调我。

看看如何使用:

   TestNet().requestNetwork(onCancel = {

                toast("test network onCancel")

            }, onFailed = {
                //先调用内部的函数处理逻辑
                it.onFailed("哎呦")  //在这里调用内部定义过的函数,如果不调用,TestNet中 YYLogUtils.w("可以随便写点什么逻辑") 不会执行

                //在打印日志
                YYLogUtils.w("test network onFailed")

            }, onSuccess = {
                //先打印日志
                YYLogUtils.w("test network onSuccess")

                //再调用内部的函数处理逻辑
                onSuccess("我的成功数据字符串")    //上面是高阶函数的调用 - 这里是高阶扩展函数的调用,同样的效果,上面需要用it调用,这里直接this 调用
                
            }, onFinished = {
                YYLogUtils.w("当前值是10,满足条件:$it")  //这里的it是那边的回调
                true  //那边是带参数返回的,这里需要返回Booble给那边
            })

到这里就开始复杂了,不过我注释的很详细了。结合打印日志应该能看懂调用流程!

五、复杂的高阶扩展函数

上面的高阶扩展函数 SuccessCallback.() -> Unit 都是最简单的使用。如果我们加大难度,如果接口中定义的是多个函数,且带参数带返回值,又如何使用呢?

上代码:

class TestNet {

    fun requestNetwork(
        onCancel: () -> Unit,   //空参和空返回值
        onFinished: ((Boolean) -> Boolean)? = null,  //带参数带返回值
        onSuccess: SuccessCallback.() -> String,   //使用高阶扩展函数的方式
        onFailed: (FailedCallback) -> Unit     //使用带参数的高阶函数的方式
    ) {

        MainScope().launch {

            val result = withContext(Dispatchers.IO) {
                delay(1500)

                return@withContext Random().nextInt(10)
            }

            YYLogUtils.w("result:$result")

            when {
                result == 10 -> {
                    val res = onFinished?.invoke(true)

                    YYLogUtils.w("接收到对面return的值 :$res")
                }
                result > 8 -> {
                    onCancel()
                }
                result > 5 -> {
                    val res = onSuccess(object : SuccessCallback {
                        override fun onSuccess(str: String): String {
                            //这里的str是外包传进来的,我们对这个字符串做处理
                            val str2 = "$str 再加一点数据"
                            YYLogUtils.w("result:$str2")
                           return str2
                        }

                        override fun doSth() {
                            YYLogUtils.w("可以随便写点什么成功之后的逻辑")
                        }
                    })

                    YYLogUtils.w("res:$res")

                }
                else -> {
                    onFailed(object : FailedCallback {    //这种接口的方式只能使用object的实现了
                        override fun onFailed(str: String) {
                            YYLogUtils.w("可以随便写点什么Failed逻辑 :$str")
                        }

                        override fun onError() {
                            YYLogUtils.w("可以随便写点什么Error逻辑")
                        }
                    })
                }
            }
        }

    }

    interface SuccessCallback {  //多个函数不能使用fun修饰了
        fun onSuccess(str: String): String

        fun doSth()
    }


    interface FailedCallback {  
        fun onFailed(str: String)

        fun onError()
    }
}

多个函数就不能使用Sam的方式来修饰接口了,但是我们使用的是高阶函数类型,所以是可以直接使用{}的。

下面看看如何使用:

            TestNet().requestNetwork(onCancel = {

                toast("test network onCancel")

            }, onFailed = {
                //先调用内部的函数处理逻辑
                it.onFailed("哎呦")  //在这里调用内部定义过的函数,如果不调用,TestNet中 YYLogUtils.w("可以随便写点什么逻辑") 不会执行

                it.onError()

                //在打印日志
                YYLogUtils.w("test network onFailed")

            }, onSuccess = {
                //先打印日志
                YYLogUtils.w("test network onSuccess")

                //再调用内部的函数处理逻辑
                onSuccess("我的成功数据字符串")    //上面是高阶函数的调用 - 这里是高阶扩展函数的调用,同样的效果,上面需要用it调用,这里直接this 调用

                doSth()

            }, onFinished = {
                YYLogUtils.w("当前值是10,满足条件:$it")  //这里的it是那边的回调
                true  //那边是带参数返回的,这里需要返回Booble给那边
            })

和上面一样的执行顺序,同样分为两级回调,一级回调还是没变,只是多个二级回调的方法而已。

日志和上面的差不多就不贴图了。

六、Kotlin的DSL的实现

DSL本意是领域特定语言,在Kotlin中就是让接口中多个函数实现的,可以直接使用{}的定义方式。

我们通过 高阶函数+高阶扩展函数 可以同样可以实现DSL的效果。

修改代码为监听回调的方式(是不是很Java)

    interface SuccessCallback {  //多个参数不能使用fun修饰了
        fun onSuccess(str: String): String

        fun doSth()
    }

    var callback: SuccessCallback? = null

    fun setOnSuccessCallback(callback: SuccessCallback) {
        this.callback = callback
    }

很普通的Java方式定义接口与监听回调,直接使用太简单了。

       testNet.setOnSuccessCallback(object : TestNet.SuccessCallback {
                override fun onSuccess(str: String): String {
                    
                }

                override fun doSth() {
             
                }
            })

那么我们修改为DSL方式:

可以看到下面类可以理解为一个桥接层,把普通的接口函数转换为一个高阶函数。

class SuccessCallbackImpl : TestNet.SuccessCallback {

    private var onSuccess: ((String) -> String)? = null

    private var doSth: (() -> Unit)? = null

    fun onSuccess(method: (String) -> String) {
        onSuccess = method
    }

    fun doSth(method: () -> Unit) {
        doSth = method
    }

    override fun onSuccess(str: String): String {
        return onSuccess?.invoke(str).toString()
    }

    override fun doSth() {
        doSth?.invoke()
    }
}

设置新的监听方法:

可以看到这里就是使用了高阶扩展函数,事件回调给 SuccessCallbackImpl 中的 onSuccess doSth函数。

fun TestNet.setOnSuccessCallbackDsl(init: SuccessCallbackImpl.() -> Unit){
    val listener = SuccessCallbackImpl()
    init(listener)
    this.setOnSuccessCallback(listener)
}

到此我们就修改一个DSL完成了,我们试试如何使用。

我们修改TestNet类中的代码,onSuccess 的回调直接通过setOnSuccessCallback 的方法来回调。

class TestNet {

    fun requestNetwork(
        onCancel: () -> Unit,   //空参和空返回值
        onFinished: ((Boolean) -> Boolean)? = null,  //带参数带返回值
        onFailed: (FailedCallback) -> Unit     //使用带参数的高阶函数的方式
    ) {

        MainScope().launch {

            val result = withContext(Dispatchers.IO) {
                delay(1500)

                return@withContext Random().nextInt(10)
            }

            YYLogUtils.w("result:$result")

            when {
                result == 10 -> {
                    val res = onFinished?.invoke(true)

                    YYLogUtils.w("接收到对面return的值 :$res")
                }
                result > 8 -> {
                    onCancel()
                }
                result > 5 -> {

                   val res =  callback?.onSuccess("success")

                    YYLogUtils.w("res:$res")

                    callback?.doSth()

                }
                else -> {
                    onFailed(object : FailedCallback {    //这种接口的方式只能使用object的实现了
                        override fun onFailed(str: String) {
                            YYLogUtils.w("可以随便写点什么Failed逻辑 :$str")
                        }

                        override fun onError() {
                            YYLogUtils.w("可以随便写点什么Error逻辑")
                        }
                    })
                }
            }
        }

    }

    interface SuccessCallback { 
        fun onSuccess(str: String): String

        fun doSth()
    }

    interface FailedCallback {  
        fun onFailed(str: String)

        fun onError()
    }

    var callback: SuccessCallback? = null

    fun setOnSuccessCallback(callback: SuccessCallback) {
        this.callback = callback
    }
}

使用:

 testNet.setOnSuccessCallbackDsl {
        onSuccess { str ->
            YYLogUtils.w("str: $str")
                str + "再加一点数据"
            }
        doSth {
                YYLogUtils.w("可以随便写点什么成功之后的逻辑")
            }
    }

以上就完成了一个DSL的改装,完美运行,当然这是接口有多个实现函数可以这么搞,如果接口只有一个函数我们可以选择直接使用高阶函数,或者使用SamType的方式定义都可以,完全就不需要这么麻烦的定义DSL了。

总结

我们从一个案例中,完成了从 扩展函数-> 高阶函数-> Sam -> 高阶扩展函数 -> DSL 等多种方案的演变。其实他们一个点都可以深挖去单独出一篇文章,这里我就不深挖了,只是演示基本的使用而已。

可以看到其实高阶函数才是这些功能的基石,也是相比Java代码更方便的一个点,大家使用的时候一定要掌握的就是高阶函数这个点。

其实每一种定义方法都能实现对应的效果,各有各有的好处。我们自己写逻辑随意就好,但是我们要看懂别人这么写的逻辑是什么,不然别人写的代码你弄不清执行顺序,逻辑都看不懂了。我们看很多框架的源码实现,或者看别人的代码逻辑。就算我们不认可,不习惯这些定义方式,但是我们至少能理解代码的调用逻辑与顺序。看懂代码逻辑。

一句话解释就是,我可以不用,但是我不能没有,我自己不这么写,但你这么写我能懂!

Ok,最后还是老样子,如有错漏请指出,如果有其他更好的方式,欢迎交流!

好了,今天就到这里了,天天这么更新扛不住了。

完结!

猜你喜欢

转载自juejin.im/post/7112333666017083406