从 Interface 到 Lambda:一次串起 Java 和 Kotlin 的回调设计

引言

最近在写自己的日志库。

设计网络模块和日志模块解耦的时候,遇到了一个很自然的问题:

网络请求完成后,怎么通知日志模块?

第一反应很简单:

定义一个 Interface。

复制代码
interface NetworkEventListener {
    fun onNetworkEvent(event: NetworkLogEvent)
}

网络模块产生事件:

复制代码
listener?.onNetworkEvent(event)

日志模块决定怎么处理。

写到这里,突然想到:

Kotlin 不是有 Lambda 和高阶函数吗?

是不是也可以这样?

复制代码
var callback: ((NetworkLogEvent) -> Unit)? = null

callback?.invoke(event)

好像也完全没问题。

继续往下想:

Interface、Listener、Callback、匿名内部类、Lambda、高阶函数、invoke......

突然发现,这些年学过的很多东西,看起来完全不同,但实际上一直在解决同一个问题。

那就是:

如何把一段行为交给别人,并在合适的时候执行。

于是,我试着从 Interface 开始,把 Java 和 Kotlin 的这条回调设计路线重新串了一遍。


一、最开始:Interface

刚学 Java 时,大概都写过这样的代码:

复制代码
interface Animal{
    void eat();
}

class Dog implements Animal{
    @Override
    public void eat(){
        System.out.println("eat");
    }
}

老师会告诉我们:

  • 抽象;

  • 多态;

  • 面向接口编程。

工作以后,又会写很多这样的接口:

复制代码
interface LogUploader{
    fun upload(logs: List<Log>)
}

或者:

复制代码
interface Cache{
    fun save()
    fun load()
}

这些接口表达的是:

我需要一种能力。

例如:

复制代码
logUploader.upload(logs)

意思就是:

请你帮我上传日志。

Cache:

复制代码
cache.save()

意思就是:

请你帮我缓存数据。

这是我们最熟悉的 Interface。


二、另一种 Interface:事件通知

最近写日志库的时候,又定义了一个接口:

复制代码
interface NetworkEventListener{
    fun onNetworkEvent(event: NetworkLogEvent)
}

这个接口和 LogUploader 有什么区别?

仔细想想:

LogUploader:

复制代码
请你帮我做一件事情。

NetworkEventListener:

复制代码
我通知你发生了一件事情。

例如:

复制代码
listener?.onNetworkEvent(event)

表示:

网络请求完成了。

通知外部。

Android 到处都是这种接口:

复制代码
OnClickListener

TextWatcher

LocationListener

SensorEventListener

例如:

复制代码
button.setOnClickListener(...)

Button 并不是说:

请你帮我点击。

而是在说:

我被点击了。

通知你一下。

原来:

Interface 不仅可以表达能力。

也可以表达事件。

而这两种场景,我以前都会写,却一直没有放在一起理解。


三、Java 是怎么做回调的?

为了理解这件事情,我们先看看最传统的 Java 回调。

定义接口:

复制代码
public interface NetworkEventListener {
    void onNetworkEvent(NetworkLogEvent event);
}

网络模块:

复制代码
public class NetworkClient {

    private NetworkEventListener listener;

    public void setNetworkEventListener(
            NetworkEventListener listener
    ){
        this.listener = listener;
    }

    public void request(){

        NetworkLogEvent event =
                new NetworkLogEvent(
                        "/login",
                        200,
                        120
                );

        if(listener != null){
            listener.onNetworkEvent(event);
        }
    }
}

外部:

复制代码
networkClient.setNetworkEventListener(
        new NetworkEventListener() {
            @Override
            public void onNetworkEvent(
                    NetworkLogEvent event
            ) {
                logger.write(event);
            }
        }
);

仔细看。

其实就是:

定义 Interface。

传进去。

以后某个时机执行。

这就是 Callback。


四、Kotlin 的 Interface 回调

Kotlin 写法更简单:

复制代码
interface NetworkEventListener{
    fun onNetworkEvent(
        event: NetworkLogEvent
    )
}

网络模块:

复制代码
class NetworkClient{

    var listener:
            NetworkEventListener? = null

    fun request(){

        val event =
            NetworkLogEvent(
                "/login",
                200,
                120
            )

        listener?.onNetworkEvent(event)
    }
}

使用:

复制代码
networkClient.listener =
    object : NetworkEventListener{

        override fun onNetworkEvent(
            event: NetworkLogEvent
        ) {
            logger.write(event)
        }
    }

虽然 Kotlin 语法简单了。

但是本质没有变化。

还是:

Interface。

Callback。

事件通知。


五、Lambda 和高阶函数

写到这里,突然想到:

Kotlin 不是可以直接这样吗?

网络模块:

复制代码
class NetworkClient{

    var onNetworkEvent:
            ((NetworkLogEvent)->Unit)? = null

    fun request(){

        val event =
            NetworkLogEvent(
                "/login",
                200,
                120
            )

        onNetworkEvent?.invoke(event)
    }
}

使用:

复制代码
networkClient.onNetworkEvent = {
    event ->
    logger.write(event)
}

突然发现:

Interface:

复制代码
listener?.onNetworkEvent(event)

高阶函数:

复制代码
onNetworkEvent?.invoke(event)

看起来已经很接近了。

本质上都是:

把一段逻辑交给别人。

以后再执行。


六、Interface 和高阶函数怎么选?

如果只有一个回调:

例如:

网络请求完成。

扫码成功。

登录成功。

高阶函数非常舒服:

复制代码
var onSuccess:
        ((User)->Unit)? = null

但是:

如果事件越来越多:

复制代码
interface NetworkEventListener{

    fun onRequestStart()

    fun onRequestSuccess()

    fun onRequestFailed()

    fun onRequestFinished()
}

会更加清晰。

如果全部用高阶函数:

复制代码
class NetworkClient(

    val onStart:(()->Unit)?,

    val onSuccess:
    ((NetworkLogEvent)->Unit)?,

    val onFailed:
    ((Throwable)->Unit)?,

    val onFinished:
    (()->Unit)?

)

当然也能写。

但是组织性不如 Interface。

所以:

高阶函数并不是取代 Interface。

而是在单回调场景下,把 Interface 写得更轻量。


七、最近写库最大的收获

最近设计日志库:

网络模块产生事件。

日志模块消费事件。

第一反应:

定义 Interface。

后来想到:

是不是可以用高阶函数?

继续想:

Listener。

Callback。

匿名内部类。

Lambda。

invoke。

突然发现:

以前总觉得:

Interface 是 Interface。

Listener 是 Listener。

Callback 是 Callback。

Lambda 是 Lambda。

高阶函数是高阶函数。

最近做项目的时候,才发现它们之间一直都有联系。

虽然语法不同。

虽然时代不同。

虽然 Java 和 Kotlin 的写法不同。

但是很多时候,它们都在解决同一个问题。

如何把一段行为交给别人,并在未来某个时机执行。


总结

最近写日志库,最大的收获并不是把日志框架写出来了。

而是突然发现:

这些年学过的:

  • Interface;

  • Listener;

  • Callback;

  • 匿名内部类;

  • Lambda;

  • 高阶函数;

  • invoke。

并不是一堆零散的知识。

它们一直都在那里,只是以不同的形式出现。

以前学 Java,学的是 Interface。

后来做 Android,学的是 Listener。

再后来学 Kotlin,学的是 Lambda 和高阶函数。

直到最近设计自己的库,才发现:

Interface 可以暴露能力:

复制代码
logUploader.upload(logs)

也可以通知事件:

复制代码
listener.onNetworkEvent(event)

高阶函数:

复制代码
callback.invoke(event)

只是另一种更加轻量的回调形式。

它们虽然写法不同,但很多时候都在完成同一件事情:

把一段行为交给别人,并在合适的时候执行。

或许,对于一个程序员来说,

从「会写这些代码」,

到「知道它们为什么这样设计」,

本身就是成长的一部分。