如何在 kotlin 优雅的封装匿名内部类(DSL、高阶函数)

匿名内部类在 Java 中是经常用到的一个特性,例如在 Android 开发中的各种 Listener,使用时也很简单,比如:


//lambda
button.setOnClickListener(v -> {
  //do some thing
});
//匿名内部类
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //do some thing
    }
});

只有一个函数的接口在 Java 和 Kotlin 中都可以很方便的使用 lambda 表达式来缩略,但是如果接口含有多个函数,使用起来就比较”不优雅“了,例如:

etString.addTextChangedListener(object :TextWatcher{
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        TODO("Not yet implemented")
    }
    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        TODO("Not yet implemented")
    }
    override fun afterTextChanged(s: Editable?) {
        TODO("Not yet implemented")
    }
})

使用起来与 Java 基本差不多,通过 object 关键字实现了一个匿名内部类,这种方法没什么大问题,例如上面的例子中,三个回调函数并非每次都要使用,很多场景可能只会用到其中一个或者几个,其余的都是空实现,每次都写这样一个匿名内部类只不过是不优雅而已。

在 Kotlin 中我们可以有两种方式实现比较优雅的使用匿名内部类:

  • DSL
  • 高阶函数

DSL

DSL 方式实现封装可以分为以下几步:

1.创建接口实现类:XxxxInterfaceDslImpl

还有上面的 TextWatcher 作为例子:

class TextWatcherDslImpl : TextWatcher {

    //原接口对应的kotlin函数对象
    private var afterTextChanged: ((Editable?) -> Unit)? = null

    private var beforeTextChanged: ((CharSequence?, Int, Int, Int) -> Unit)? = null

    private var onTextChanged: ((CharSequence?, Int, Int, Int) -> Unit)? = null

    /**
     * DSL中使用的函数,一般保持同名即可
     */
    fun afterTextChanged(method: (Editable?) -> Unit) {
        afterTextChanged = method
    }

    fun beforeTextChanged(method: (CharSequence?, Int, Int, Int) -> Unit) {
        beforeTextChanged = method
    }

    fun onTextChanged(method: (CharSequence?, Int, Int, Int) -> Unit) {
        onTextChanged = method
    }

    /**
     * 实现原接口的函数
     */
    override fun afterTextChanged(s: Editable?) {
        afterTextChanged?.invoke(s)
    }

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

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

这个实现类由三个部分组成:

  1. 原接口方法对应的 Kotlin 函数对象,函数对象的签名与对应的方法签名保持一致
  2. DSL 函数,函数名称、签名都与原接口的方法一一对应,用于接收 lambda 赋值给 Kotlin 函数对象
  3. 原接口方法的实现,每个接口方法的实现,都是对实现类中 Kotlin 函数对象的调用
2.创建与原函数同名的扩展函数,函数参数为实现类扩展函数
fun TextView.addTextChangedListenerDsl(init: TextWatcherDslImpl.() -> Unit) {
    val listener = TextWatcherDslImpl()
    listener.init()
    this.addTextChangedListener(listener)
}

扩展函数与原函数同名可以方便使用者调用,无需记忆其他函数名,如果担心混淆,可以在函数名后加上 Dsl 用以区分。该函数的参数是我们第一步创建的实现类的扩展函数,这是为了实现 DSL 语法。

3.使用
etString.addTextChangedListenerDsl {
    afterTextChanged {
        if (it.toString().length >= 4) {
            KeyboardUtils.toggleSoftInput()
        }
    }
}

使用这种方式时,可以说相当之优雅,我们只需要调用我们需要实现的接口方法即可,不需要使用的接口方法默认空实现。

高阶函数

高阶函数方式比 DSL 方式更简单一点:

inline fun TextView.addTextChangedListenerClosure(
    crossinline afterTextChanged: (Editable?) -> Unit = {},
    crossinline beforeTextChanged: (CharSequence?, Int, Int, Int) -> Unit = { charSequence, start, count, after -> },
    crossinline onTextChanged: (CharSequence?, Int, Int, Int) -> Unit = { charSequence, start, after, count -> }
) {
    val listener = object : TextWatcher {
        override fun afterTextChanged(s: Editable?) {
            afterTextChanged.invoke(s)
        }

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

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            onTextChanged.invoke(s, start, before, count)
        }
    }
    this.addTextChangedListener(listener)
}

我们创建一个同名扩展函数,使用 Closure 尾缀作为区分,该函数的参数为与接口方法一一对应的 Kotlin 函数对象,并给其默认值赋值为 {} 即空实现,在函数体里通过 object 关键字构建匿名内部类实现对象,在其接口方法实现中调用与之一一对应的 Kotlin 函数对象。

使用方式上与普通的 Kotlin 高阶函数使用方式相同:

etString.addTextChangedListenerClosure(
    afterTextChanged = {
        if (it.toString().length >= 4) {
            KeyboardUtils.toggleSoftInput()
        }
    },
)
tips:

上面示例的扩展函数中,我们使用了 inline 与 crossinline 两个关键字,这是 Koltin 特有的。inline 关键字通常用于修饰高阶函数,用于提升性能。crossinline 声明的 lambda 不允许局部返回,用于避免调用者错误的使用 return 导致函数中断。

提供一个示例代码,亲自尝试一下也许可以更好的理解:

@Test
fun testInline() {
    testClosure {
        return
    }
}
private inline fun testClosure(test: (String) -> String ) {
    println("step 1")
    println(test("step test"))
    println("step 2")
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值