引言
最近在写自己的日志库。
设计网络模块和日志模块解耦的时候,遇到了一个很自然的问题:
网络请求完成后,怎么通知日志模块?
第一反应很简单:
定义一个 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)
只是另一种更加轻量的回调形式。
它们虽然写法不同,但很多时候都在完成同一件事情:
把一段行为交给别人,并在合适的时候执行。
或许,对于一个程序员来说,
从「会写这些代码」,
到「知道它们为什么这样设计」,
本身就是成长的一部分。