简介
响应式编程是一种以异步数据流为核心的编程方式。这里的数据一般是一些事件,而流则是在时间序列上的一系列的事件。任何东西都可以转化为数据流,如变量、用户输入事件、数据结构等。
我们可以很灵活地操纵数据流,如可以将两个甚至多个数据流融合成一个数据流,可以从数据流中过滤出感兴趣的事件,还可以将数据流中的事件转化为其他新的事件。
数据流中的事件通常可以分成三种类型:普通事件、错误事件和结束事件。以用户的键盘输入事件为例,当用户依次敲击“A”、“B”、“C”键的时候,就会产生三个输入事件,计算机接收到这些事件并对其做出响应一一将字母“ A”、“B ”、“ C”显示在显示器上。当用户敲击回车键时,可以将其作为一个结束事件来表示数据流的结束,即用户输入结束。而在输入过程中发生的任何错误都可以作为数据流中的错误事件。
响应式编程使用场景
想象这样一个场景:用户登录一个购物客户端,这时客户端会将自己的用户名和密码通过网络请求发送出去,然后通过注册一个回调接口来监听请求的结果,因为结果只有成功和失败两种,所以回调里需要有 onSuccess 和 onError 两个接口来分别处理这两种情况,如下代码所示。
1 2 3 4 5 6 7 8 9 10 11 |
void sendRequest(String userName,Str 工ng passwrod) { client.sendLoginRequest(userName, passwrod, new Callback() { public void onSuccess(Userinfo info) { // 处理登录成功的操作 } public void onError(Exception e) { // 处理错误的操作 } }); } |
通过注册一个回调接口来监听请求的结果,这其实也算是一种响应式编程。
如果在登录成功后,我们要根据用户的 ID 来请求用户的购物记录,该如何操作呢?第一个会想到的就是在 onSuccess 方法里面继续请求。同登录请求一样,也需要注册一个回调接口来监听请求的结果,那我们继续在上面代码的基础上添加代码,如下代码所示。登录成功后我们发出了查询购物记录的请求,并在 onSuccess 方法里将购物记录展示到 UI 上面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void sendRequest(String userName,String passwrod) { client.sendLoginRequest(userName, passwrod, new LoginCallback() { public void onSuccess(Userinfo info) { client.sendRecordRequest(info.ID, new RecordCallback() { public void onSuccess(List<Record> list) { // 在 UI 上展示购物记录 view.update(list); } public void onError(Exception e){ // 处理错误的操作 } }); public void onError(Exception e) { // 处理错误的操作 } }); } |
需求进展到这,除了回调的嵌套外,好像还行。
这时如果有更进一步的需求:要求只展示最近一个月的购物记录(不考虑服务器端的实现),该怎么办呢?看来只能继续在 onSuccess 方法里改了,如下代码所示。为了看起来简单,这里忽略了外层的嵌套。
1 2 3 4 5 6 7 8 9 |
public void onSuccess (List<Record> list) { Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { Record record = iterator.next(); if (record.time < getTime()) iterator.remove(); } // 在 UI 上展示购买记 } |
紧接着新的需求又来了:每条购买记录里可能包含多个物品 ,之前只展示了每条购买记录,现在我们需要将所有购买记录里的所有物品都展示到 UI 上。继续改,如下代码所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public void onSuccess (List<Record> list) { Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { Record record = iterator.next(); 工 f (record.time < getTime()) iterator.remove(); } List<Item> itemList = new ArrayList<>(); for (Record record : list) { for (Item item : record.getitemList()) { itemList.add(item); } } // 在 UI 上展示物品记录 view.update(itemList); } |
终于再次改完了 ,但是还可能有其他需求出现,如只显示金额大于 100 元的物品、将所有相同的物品合并后显示、有些物品需要请求网络获取更详细的信息,等等。
到这里,我们就不会还像最初那么淡定了。
传统的命令式编程在处理这样的需求时就是非常痛苦的,但是使用响应式编程就可以游刃有余地处理。如果使用响应式编程,这些要求可以很容易地串成一条链式调用,如下所示。

通过上面的实例和链式调用图,我们可以看到响应式编程提高了代码的抽象层级,所以我们只需要关注与业务逻辑相关的事件,而不必去纠缠里面的一些细节。
不仅如此,响应式编程的链式调用还可以消除回调嵌套,所以使用响应式编程写出来的代码往往会更加简明易懂。
响应式编程非常适合以下几种情况:
- 鼠标的点击、移动事件,键盘的输入事件,移动设备上的各种触摸和手势事件 。
- 当用户的位置改变时,其移动设备上的 GPS 信号和陀螺仪信号 。
- 各种耗时的操作,如读取硬盘内容以及从网络请求数据,这些操作一般都是异步的 。
- 涉及数据的转化、组合、过滤等操作的场景 。