我所理解的响应式编程

我所理解的响应式编程

http://blog.csdn.net/liang3472/article/details/46445529

函数响应式编程(FRP Functional Reactive Programming),为解决现代编程问题提供了全新的视角.一旦理解它,可以极大地简化你的项目,特别是处理嵌套回调的异步事件,复杂的列表过滤和变换,或者时间相关问题。

我们现在大多使用的是命令式编程,命令式编程与函数相应式编程的区别如下:

命令式编程:以命令为主,给机器提供一条又一条的命令序列让其原封不动的执行。

函数响应式编程(FRP):使用异步数据流进行编程。FRP的思想比较难理解,需要我们将以往的命令式编程思想转变为响应式编程思想。我们要做的就是面向数据流编程。Everything is a stream

就像我们熟知的面向对象思想一样,把事物都看作Stream。变量、用户输入、属性、Cache、数据结构等等。这个开始可能会很难。

在命令式编程环境中,a:=b+c表示将表达式的结果赋给a,而之后改变b或c的值不会影响a。但在响应式编程中,a的值会随着b或c的更新而更新。e.g 电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似"=B1+C1"的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。这很类似观察者模式(Gof)。函数响应式编程的重点是流,Stream能接受一个,甚至多个Stream为输入。你可以merge两个Stream,也可以从一个Stream中filter出你感兴趣的Events以生成一个新的Stream,还可以把一个Stream中的Data values map到一个新的Stream中。

以上网上都有概括,我就不都说了,那么直接切入主题吧。就拿我项目中遇到的问题来说吧。我们这客户端的需求是这样的,当用户填写完地址后,点击提交按钮,将对地址进行反地理编码(就是将用户输入的地址转换成经纬度坐标)。然后调用上传接口将经纬度上传到后台服务器。

用户输入完地址点击提交按钮---调用反地理编码功能获取经纬度(asyn)---将经纬度提交后台服务器(asyn)

bean

 

[html] view plain copy
 
  1. package com.liang.frpdemo;  
  2.   
  3. /**  
  4.  * 地理位置bean  
  5.  * @author tomliang  
  6.  *  
  7.  */  
  8. public class LocationBean {  
  9.     public int lon;  
  10.     public int lat;  
  11. }  
操作接口

 

 

[html] view plain copy
 
  1. package com.liang.frpdemo;  
  2.   
  3. /**  
  4.  * 操作接口  
  5.  * @author tomliang  
  6.  */  
  7. public interface Api {  
  8.     /**  
  9.      * 将输入的地址反地理编码成经纬度  
  10.      * @param address  
  11.      * @return  
  12.      */  
  13.     LocationBean getLocation(String address);  
  14.     /**  
  15.      * 将经纬度提交给服务器  
  16.      * @param bean  
  17.      */  
  18.     void submitLocation(LocationBean bean);  
  19. }  
业务逻辑helper类

 

 

[html] view plain copy
 
  1. package com.liang.frpdemo;  
  2.   
  3. public class LocationHelper {  
  4.   
  5.     private Api api;  
  6.     private static LocationHelper helper = new LocationHelper();  
  7.       
  8.     private LocationHelper() {  
  9.     }  
  10.       
  11.     public static LocationHelper getHelper(){  
  12.         return helper;  
  13.     }  
  14.       
  15.     void commit(String address){  
  16.         try {  
  17.             LocationBean location = api.getLocation(address);  
  18.             api.submitLocation(location);  
  19.         } catch (Exception e) {  
  20.             e.printStackTrace();  
  21.         }  
  22.     }  
  23.   
  24. }  

 

代码简单易懂,commit方法实现了获取地理位置和提交功能的组合,这种组合方法简单易懂。只要在最外层捕捉异常就能做统一处理。

但是这种阻塞式风格明显不符合需求,请求地理位置、提交这些都应该是异步的,所以我们必须优化代码。我们最常干的事就是写回调。

 

[html] view plain copy
 
  1. package com.liang.frpdemo;  
  2.   
  3. /**  
  4.  * 操作接口  
  5.  * @author tomliang  
  6.  */  
  7. public interface Api {  
  8.     /**  
  9.      * 将输入的地址反地理编码成经纬度  
  10.      * @param address  
  11.      * @param getCallBack  
  12.      */  
  13.     void getLocation(String address, LocationCallBack getCallBack);  
  14.     /**  
  15.      * 将经纬度提交给服务器  
  16.      * @param submitCallBack  
  17.      */  
  18.     void submitLocation(LocationBean bean, SubmitCallBack submitCallBack);  
  19.       
  20.     /**  
  21.      * 获取地理位置回调  
  22.      * @author tomliang  
  23.      *  
  24.      */  
  25.     interface LocationCallBack{  
  26.         void onLocationReceived(LocationBean bean);  
  27.         void onError();  
  28.     }  
  29.     /**  
  30.      * 提交位置回调  
  31.      * @author tomliang  
  32.      *  
  33.      */  
  34.     interface SubmitCallBack{  
  35.         void onSubmitReceived();  
  36.         void onError();  
  37.     }  
  38. }  
然后我们的helper类变成了

 

 

[html] view plain copy
 
  1. package com.liang.frpdemo;  
  2.   
  3. import com.liang.frpdemo.Api.LocationCallBack;  
  4. import com.liang.frpdemo.Api.SubmitCallBack;  
  5.   
  6. public class LocationHelper {  
  7.   
  8.     private Api api;  
  9.     private static LocationHelper helper = new LocationHelper();  
  10.       
  11.     private LocationHelper() {  
  12.         api = new DefaultApi();  
  13.     }  
  14.       
  15.     public static LocationHelper getHelper(){  
  16.         return helper;  
  17.     }  
  18.       
  19.     void commit(String address, final CommitCallBack callback){  
  20.         api.getLocation(address, new LocationCallBack() {  
  21.                   
  22.             @Override  
  23.             public void onLocationReceived(LocationBean bean) {  
  24.                 api.submitLocation(bean, new SubmitCallBack() {  
  25.                       
  26.                     @Override  
  27.                     public void onSubmitReceived() {  
  28.                         callback.onCommitReceived();  
  29.                     }  
  30.                       
  31.                     @Override  
  32.                     public void onError() {  
  33.                         callback.onError();  
  34.                     }  
  35.                 });  
  36.             }  
  37.                   
  38.             @Override  
  39.             public void onError() {  
  40.                 callback.onError();  
  41.             }  
  42.         });  
  43.     }  
  44.   
  45.     public interface CommitCallBack{  
  46.         void onCommitReceived();  
  47.         void onError();  
  48.     }  
  49. }  
逻辑没变,但是看完这些回调是不是感觉不再爱了。组合已经没有了,错误需要我们手动一级级向外传递。接着优化

 

以上回调分为两种

1.onLocationReceived,onSubmitReceived,onCommitReceived

2.onError

可以将这些回调抽取出来作为公共回调

 

[html] view plain copy
 
  1. package com.liang.frpdemo;  
  2.   
  3. public interface CallBack<T> {  
  4.       
  5.     void onResult(T result);  
  6.     void onError();  
  7.       
  8. }  
新建一个ApiWrapper转换一下调用

 

 

[html] view plain copy
 
  1. package com.liang.frpdemo;  
  2.   
  3. import com.liang.frpdemo.Api.LocationCallBack;  
  4. import com.liang.frpdemo.Api.SubmitCallBack;  
  5.   
  6. public class ApiWrapper {  
  7.   
  8.     Api api;  
  9.       
  10.     public ApiWrapper(){  
  11.         api = new DefaultApi();  
  12.     }  
  13.       
  14.     public void getLocation(String address, final CallBack<LocationBean> callback){  
  15.         api.getLocation(address, new LocationCallBack() {  
  16.               
  17.             @Override  
  18.             public void onLocationReceived(LocationBean bean) {  
  19.                 callback.onResult(bean);  
  20.             }  
  21.               
  22.             @Override  
  23.             public void onError() {  
  24.                 callback.onError();  
  25.             }  
  26.         });  
  27.     }  
  28.       
  29.     public void submitLocation(LocationBean bean, final CallBack<Void> callback){  
  30.         api.submitLocation(bean, new SubmitCallBack() {  
  31.               
  32.             @Override  
  33.             public void onSubmitReceived() {  
  34.                 callback.onResult(null);  
  35.             }  
  36.               
  37.             @Override  
  38.             public void onError() {  
  39.                 callback.onError();  
  40.             }  
  41.         });  
  42.     }  
  43. }  
修改helper

 

 

[html] view plain copy
 
  1. package com.liang.frpdemo;  
  2.   
  3. public class LocationHelper {  
  4.   
  5.     private ApiWrapper api;  
  6.     private static LocationHelper helper = new LocationHelper();  
  7.       
  8.     private LocationHelper() {  
  9.         api = new ApiWrapper();  
  10.     }  
  11.       
  12.     public static LocationHelper getHelper(){  
  13.         return helper;  
  14.     }  
  15.       
  16.     void commit(String address, final CallBack<Void> callback){  
  17.         api.getLocation(address, new CallBack<LocationBean>() {  
  18.               
  19.             @Override  
  20.             public void onResult(LocationBean result) {  
  21.                 api.submitLocation(result, callback);  
  22.             }  
  23.               
  24.             @Override  
  25.             public void onError() {  
  26.                 callback.onError();  
  27.             }  
  28.         });  
  29.     }  
代码上相比之前是减少了不少,因为我们通过ApiWrapper来减少了回调间的层级调用。

 

注意前方高能

分析一下我们的回调形式,发现有一个共同特点有木有,(getLocation,submitLocation,commit)这些函数参数形式是一个参数一个回调对象。我们的优化是要在这些异步操作中返回一些临时对象。我们需要定义一个公共的对象来为异步操作作为返回对象。just do IT.

 

[html] view plain copy
 
  1. package com.liang.frpdemo;  
  2.   
  3. public abstract class AsynJob<T> {  
  4.     public abstract void start(CallBack<T> callback);  
  5. }  
将异步的方法返回值变为该对象

 

 

[html] view plain copy
 
  1. package com.liang.frpdemo;  
  2.   
  3. import com.liang.frpdemo.Api.LocationCallBack;  
  4. import com.liang.frpdemo.Api.SubmitCallBack;  
  5.   
  6. public class ApiWrapper {  
  7.   
  8.     Api api;  
  9.       
  10.     public ApiWrapper(){  
  11.         api = new DefaultApi();  
  12.     }  
  13.       
  14.     public AsynJob<LocationBean> getLocation(final String address){  
  15.         return new AsynJob<LocationBean>() {  
  16.               
  17.             @Override  
  18.             public void start(final CallBack<LocationBean> callback) {  
  19.                 api.getLocation(address, new LocationCallBack() {  
  20.                       
  21.                     @Override  
  22.                     public void onLocationReceived(LocationBean bean) {  
  23.                         callback.onResult(bean);  
  24.                     }  
  25.                       
  26.                     @Override  
  27.                     public void onError() {  
  28.                         callback.onError();  
  29.                     }  
  30.                 });  
  31.             }  
  32.         };  
  33.     }  
  34.       
  35.     public AsynJob<Void> submitLocation(final LocationBean bean){  
  36.         return new AsynJob<Void>() {  
  37.               
  38.             @Override  
  39.             public void start(final CallBack<Void> callback) {  
  40.                 api.submitLocation(bean, new SubmitCallBack() {  
  41.                       
  42.                     @Override  
  43.                     public void onSubmitReceived() {  
  44.                         callback.onResult(null);  
  45.                     }  
  46.                       
  47.                     @Override  
  48.                     public void onError() {  
  49.                         callback.onError();  
  50.                     }  
  51.                 });  
  52.             }  
  53.         };  
  54.     }  
  55. }  
同样更改helper

 

 

[html] view plain copy
 
  1. package com.liang.frpdemo;  
  2.   
  3. public class LocationHelper {  
  4.   
  5.     private ApiWrapper api;  
  6.     private static LocationHelper helper = new LocationHelper();  
  7.       
  8.     private LocationHelper() {  
  9.         api = new ApiWrapper();  
  10.     }  
  11.       
  12.     public static LocationHelper getHelper(){  
  13.         return helper;  
  14.     }  
  15.       
  16.     AsynJob<Void> commit(final String address){  
  17.         return new AsynJob<Void>() {  
  18.               
  19.             @Override  
  20.             public void start(final CallBack<Void> callback) {  
  21.                 api.getLocation(address).start(new CallBack<LocationBean>() {  
  22.                       
  23.                     @Override  
  24.                     public void onResult(LocationBean result) {  
  25.                         api.submitLocation(result).start(new CallBack<Void>() {  
  26.                               
  27.                             @Override  
  28.                             public void onResult(Void result) {  
  29.                                 callback.onResult(null);  
  30.                             }  
  31.                               
  32.                             @Override  
  33.                             public void onError() {  
  34.                                 callback.onError();  
  35.                             }  
  36.                         });  
  37.                     }  
  38.                       
  39.                     @Override  
  40.                     public void onError() {  
  41.                         callback.onError();  
  42.                     }  
  43.                 });  
  44.             }  
  45.         };  
  46.     }  
  47.   
  48. }  

现在感觉逻辑结构清晰了点。

我们再来试试将代码分解成更小

 

[html] view plain copy
 
  1. package com.liang.frpdemo;  
  2.   
  3. public class LocationHelper {  
  4.   
  5.     private ApiWrapper api;  
  6.     private static LocationHelper helper = new LocationHelper();  
  7.       
  8.     private LocationHelper() {  
  9.         api = new ApiWrapper();  
  10.     }  
  11.       
  12.     public static LocationHelper getHelper(){  
  13.         return helper;  
  14.     }  
  15.       
  16.     AsynJob<Void> commit(final String address){  
  17.         final AsynJob<LocationBeanlocationJob = api.getLocation(address);  
  18.           
  19.         AsynJob<VoidsubmitJob = new AsynJob<Void>() {  
  20.               
  21.             @Override  
  22.             public void start(final CallBack<Void> callback) {  
  23.                 locationJob.start(new CallBack<LocationBean>() {  
  24.                       
  25.                     @Override  
  26.                     public void onResult(LocationBean result) {  
  27.                         api.submitLocation(result).start(new CallBack<Void>(){  
  28.                               
  29.                             @Override  
  30.                             public void onResult(Void result) {  
  31.                                 callback.onResult(result);  
  32.                             }  
  33.                               
  34.                             @Override  
  35.                             public void onError() {  
  36.                                 callback.onError();  
  37.                             }  
  38.                         } );  
  39.                     }  
  40.                       
  41.                     @Override  
  42.                     public void onError() {  
  43.                         callback.onError();  
  44.                     }  
  45.                 });  
  46.             }  
  47.         };  
  48.         return submitJob;  
  49.     }  
  50.   
  51. }  
好像清晰得不够明显啊。

 



高能要来了

[html] view plain copy
 
  1. AsynJob<VoidsubmitJob = new AsynJob<Void>() {  
  2.               
  3.             @Override  
  4.             public void start(final CallBack<Void> callback) {  
  5.                 locationJob.start(new CallBack<LocationBean>() {  
  6.                       
  7.                     @Override  
  8.                     public void onResult(LocationBean result) {  
  9.                         api.submitLocation(result).start(new CallBack<Void>(){  
  10.                               
  11.                             @Override  
  12.                             public void onResult(Void result) {  
  13.                                 callback.onResult(result);  
  14.                             }  
  15.                               
  16.                             @Override  
  17.                             public void onError() {  
  18.                                 callback.onError();  
  19.                             }  
  20.                         } );  
  21.                     }  
  22.                       
  23.                     @Override  
  24.                     public void onError() {  
  25.                         callback.onError();  
  26.                     }  
  27.                 });  
  28.             }  
  29.         };  

在这个方法里,回调的层级还是太多了,下面所要做的就是在这里做优化。

在以上代码中,回调基本都是一致的onResult和onError,我们要将回调和功能代码进行分离。

在java中,我们不能将方法作为参数传递,所以我们需要通过类(和接口)来简介实现这样的功能。

 

[html] view plain copy
 
  1. package com.liang.frpdemo;  
  2.   
  3. public interface Func<T, R> {  
  4.     R call(T t);  
  5. }  
T对应于参数类型,R对应于返回类型。

 

接下来,我们来改造一下AsynJob

 

[html] view plain copy
 
  1. package com.liang.frpdemo;  
  2.   
  3. public abstract class AsynJob<T> {  
  4.     public abstract void start(CallBack<T> callback);  
  5.       
  6.     public <R> AsynJob<R> map(final Func<T, AsynJob<R>> func){  
  7.         final AsynJob<Tsource = this;  
  8.         return new AsynJob<R>() {  
  9.   
  10.             @Override  
  11.             public void start(final CallBack<R> callback) {  
  12.                 source.start(new CallBack<T>() {  
  13.   
  14.                     @Override  
  15.                     public void onResult(T result) {  
  16.                         AsynJob<Rmapped = func.call(result);  
  17.                         mapped.start(new CallBack<R>() {  
  18.                               
  19.                             @Override  
  20.                             public void onResult(R result) {  
  21.                                 callback.onResult(result);  
  22.                             }  
  23.                               
  24.                             @Override  
  25.                             public void onError() {  
  26.                                 callback.onError();  
  27.                             }  
  28.                         });  
  29.                     }  
  30.   
  31.                     @Override  
  32.                     public void onError() {  
  33.                         callback.onError();  
  34.                     }  
  35.                 });  
  36.             }  
  37.         };  
  38.     }  
  39. }  
再来看看helper

 

 

[html] view plain copy
 
  1. package com.liang.frpdemo;  
  2.   
  3. public class LocationHelper {  
  4.   
  5.     private ApiWrapper api;  
  6.     private static LocationHelper helper = new LocationHelper();  
  7.       
  8.     private LocationHelper() {  
  9.         api = new ApiWrapper();  
  10.     }  
  11.       
  12.     public static LocationHelper getHelper(){  
  13.         return helper;  
  14.     }  
  15.       
  16.     AsynJob<Void> commit(final String address){  
  17.         final AsynJob<LocationBeanlocationJob = api.getLocation(address);  
  18.           
  19.         AsynJob<VoidsubmitJob = locationJob.map(new Func<LocationBean, AsynJob<Void>>() {  
  20.               
  21.             @Override  
  22.             public AsynJob<Void> call(LocationBean t) {  
  23.                 return api.submitLocation(t);  
  24.             }  
  25.         });  
  26.           
  27.         return submitJob;  
  28.     }  
  29.   
  30. }  
是不是感觉很帅,这风骚的代码风格。

 

上面一步步的优化过程其实是RxJava功能的冰山一角。

AsynJob<T>实际上就是Observable,它不止可以只分发一个单一的结果也可以是一个序列(可以为空)。

CallBack<T>就是Observer,除了CallBack少了一个onNext(T t)方法。Observer中在onError(Throwable t)方法被调用后,会继而调用onCompleted(),然后Observer会包装好病发送出事件流(因为它能发送一个序列)。

abstract void start(Callback<T> callback)对应Subscription subscribe(final Observer<? super T> observer),这个方法也返回Subscription,不需要它时你可以决定取消接收事件流。

 

来看看我使用了RxAndroid后,代码的变化

 

[html] view plain copy
 
  1. package com.liang.frpdemo;  
  2.   
  3. import rx.Observable;  
  4. import rx.Subscriber;  
  5.   
  6. /**  
  7.  * Created by Administrator on 2015/6/16.  
  8.  */  
  9. public class NewApiWrapper {  
  10.     Api api;  
  11.   
  12.     public NewApiWrapper(){  
  13.         api = new DefaultApi();  
  14.     }  
  15.   
  16.     public Observable<LocationBean> getLocation(final String address){  
  17.         return Observable.create(new Observable.OnSubscribe<LocationBean>() {  
  18.             @Override  
  19.             public void call(final Subscriber<? super LocationBean> subscriber) {  
  20.                 api.getLocation(address, new Api.LocationCallBack() {  
  21.   
  22.                     @Override  
  23.                     public void onLocationReceived(LocationBean bean) {  
  24.                         subscriber.onNext(bean);  
  25.                     }  
  26.   
  27.                     @Override  
  28.                     public void onError(Throwable e) {  
  29.                         subscriber.onError(e);  
  30.                     }  
  31.                 });  
  32.             }  
  33.         });  
  34.     }  
  35.   
  36.     public Observable<Void> submitLocation(final LocationBean bean){  
  37.         return Observable.create(new Observable.OnSubscribe<Void>() {  
  38.             @Override  
  39.             public void call(final Subscriber<? super Void> subscriber) {  
  40.                 api.submitLocation(bean, new Api.SubmitCallBack() {  
  41.   
  42.                     @Override  
  43.                     public void onSubmitReceived() {  
  44.                         subscriber.onNext(null);  
  45.                     }  
  46.   
  47.                     @Override  
  48.                     public void onError(Throwable e) {  
  49.                         subscriber.onError(e);  
  50.                     }  
  51.                 });  
  52.             }  
  53.         });  
  54.     }  
  55. }  
helper的变化

 

 

[html] view plain copy
 
  1. package com.liang.frpdemo;  
  2.   
  3. import rx.Observable;  
  4. import rx.Subscriber;  
  5. import rx.functions.Func1;  
  6.   
  7. public class NewLocationHelper {  
  8.   
  9.     private NewApiWrapper api;  
  10.     private static NewLocationHelper helper = new NewLocationHelper();  
  11.   
  12.     private NewLocationHelper() {  
  13.         api = new NewApiWrapper();  
  14.     }  
  15.       
  16.     public static NewLocationHelper getHelper(){  
  17.         return helper;  
  18.     }  
  19.   
  20.     Observable<Void> commit(final String address){  
  21.         Observable<LocationBeanlocationJob = api.getLocation(address);  
  22.         Observable<VoidsubmitJob = locationJob.flatMap(new Func1<LocationBean, Observable<Void>>() {  
  23.             @Override  
  24.             public Observable<Void> call(LocationBean locationBean) {  
  25.                 return api.submitLocation(locationBean);  
  26.             }  
  27.         });  
  28.         return submitJob;  
  29.     }  
  30.   
  31. }  

是不是逻辑清晰多了,代码简洁了很多。

源码地址


除了RxJava,RxAndroid,还有

 

详情请点这里

写得不好,欢迎吐槽。博客参考自

NotRxJava懒人专用指南

 

版权声明:本文为博主原创文章,未经博主允许不得转载。
posted @ 2017-10-25 15:06  sky20080101  阅读(178)  评论(0编辑  收藏  举报