从 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)只是另一种更加轻量的回调形式。
它们虽然写法不同,但很多时候都在完成同一件事情:
把一段行为交给别人,并在合适的时候执行。
或许,对于一个程序员来说,
从「会写这些代码」,
到「知道它们为什么这样设计」,
本身就是成长的一部分。
