持续创作,加速成长!这是我参与「掘金日新计划 · 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支持把函数当做参数类型,看看如何定义:
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,最后还是老样子,如有错漏请指出,如果有其他更好的方式,欢迎交流!
好了,今天就到这里了,天天这么更新扛不住了。
完结!