自己动手,链式编程

参考# Bolts-Android想深入了解的同学可以直接下载下来研究。

举个粟子

在实际的开发过程中,可能会遇到这样的情况,页面的展示需要一些数据,这些数据需要调用不同的接口A、B、C、来获取,且必须按有顺序调用,因为B接口需要A接口的返回数据 ,C接口需要B接口的返回数据。

这个时候,你的代码可能是这样的:

    private void fetchData() {
        callA("a");
    }

    private void callA(String param) {
        //fetch A data, then call B
        callB("b");
    }

    private void callB(String param) {
        //fetch B data, then call C
        callC("c");
    }

    private void callC(String param) {
        //fetch c data
        //then display
    }

这样写逻辑上没有问题,功能上也没有问题。但是看代码的时候就有点难受了,很难从宏观上理解,做这个功能,怎样的一个流程,只能点进各个方法里去看,然后跳转,再跳转......

当你理解我上面在说什么时候,你肯定会想办法,让代码看起来更清晰明了,可能你会有这样的想法:

public interface IResult<T> {
    void onResult(T data);
}

    private void fetchData() {
        callA("a", new IResult<String>() {
            @Override
            public void onResult(String data) {
                callB(data, new IResult<String>() {
                    @Override
                    public void onResult(String data) {
                        callC(data, new IResult<String>() {
                            @Override
                            public void onResult(String data) {
                                //then display
                            }
                        });
                    }
                });
            }
        });
    }

    private void callA(String param, IResult<String> callback) {
        //fetch A data, then callback
        callback.onResult("b");
    }

    private void callB(String param, IResult<String> callback) {
        //fetch B data, then callback
        callback.onResult("c");
    }

    private void callC(String param, IResult<String> callback) {
        //fetch C data, then callback
        callback.onResult("success");
    }

这样写之后,就可以在fetchData()里清楚明白的知道,需要调用接口A、B、C之后,再进行展示的流程。但是多层的嵌套回调,虽然流程在一个方法里,但是看起来还是有点费劲。可以再作一点优化:

private void fetchData() {
        final IResult<String> cCallback = new IResult<String>() {
            @Override
            public void onResult(String data) {
                //then display
            }
        };

        final IResult<String> bCallback = new IResult<String>() {
            @Override
            public void onResult(String data) {
                callC(data, cCallback);
            }
        };
        
        IResult<String> aCallback = new IResult<String>() {
            @Override
            public void onResult(String data) {
                callB(data, bCallback);
            }
        };
        
        callA("a", aCallback);
    }

看到这里,你可能会想,上面写的一堆跟我最开头参考的东西,有什么关系?

确实也没什么关系。

不过,我想通过上面的粟子,来引出链式编程。

试想,如果我们的代码可以像我们的语言表达那样,先这样,再这样,再这样,最后这样,这样写代码的话,是不是会更容易理解一些?下面就是一个链式的举例:

        Task.call(new Callable<String>() {
            @Override
            public String call() {
                return callA("a");
            }
        }).continueWith(new Continuation<String, String>() {
            @Override
            public String then(Task<String> task) {
                return callB(task.getResult());
            }
        }).continueWith(new Continuation<String, String>() {
            @Override
            public String then(Task<String> task) {
                return callC(task.getResult());
            }
        }).continueWith(new Continuation<String, Void>() {
            @Override
            public Void then(Task<String> task) {
                //display
                return null;
            }
        });

根据上面的链式例子,我们分析一下,想要进行链式编程,要编写一个处理各位任务的类Task,其中有一个静态方法call(),这个方法需要传入一个Callable参数,返回一个Task实例,方便链式的调用其他方法continueWith(),这个continueWith方法需要传入一个Continuation的实例,返回一个Task实例。

了解之后,那么我们来自己写一个的简单的链式处理类

定义一个处理类MyTask,写两个方法

public class MyTask<T> {
    private T result;

    public static <TResult> MyTask<TResult> doSth(Callable<TResult> callable) {
        return new MyTask<>();
    }

    public <TContinueResult> MyTask<TContinueResult> continueDoSth(MyContinuation<T, TContinueResult> continuation) {
        return new MyTask<>();
    }

    public T getResult() {
        return result;
    }
}

可以看到上面的方法里直接new Task<>()返回了,因为不着急处理,想先看看效果。

        MyTask.doSth(new Callable<String>() {
            @Override
            public String call() {
                return callA("a");
            }
        }).continueDoSth(new MyContinuation<String, String>() {
            @Override
            public void then(MyTask<String> task) {
                callB(task.getResult());
            }
        }).continueDoSth(new MyContinuation<String, String>() {
            @Override
            public void then(MyTask<String> task) {
                callC(task.getResult());
            }
        }).continueDoSth(new MyContinuation<String, String>() {
            @Override
            public void then(MyTask<String> task) {
                //display
            }
        });

MyContinuation.java

public interface MyContinuation<TTaskResult, TContinuationResult> {
    TContinuationResult then(MyTask<TTaskResult> task);
}

现在,在具体使用时的写法上,已经跟 Bolts-Android里的一部分差不多了,就是在具体的处理类的实现上并没有进行实现。
MyTask .doSth方法,需要执行传入的Callable并把Callable的执行结果写到一个实例化的MyTask里。那么现在我们改造一下MyTask类里的doSth方法。

public class MyTask<T> {
    private T result;

    public static <TResult> MyTask<TResult> doSth(Callable<TResult> callable) {
        MyTask<TResult> task = new MyTask<>();
        try {
            task.result = callable.call();
        } catch (Exception e) {
            e.printStackTrace();
            task.result = null;
        }
        return task;
    }

    public <TContinueResult> MyTask<TContinueResult> continueDoSth(MyContinuation<T, TContinueResult> continuation) {
        MyTask<TContinueResult> task = new MyTask<>();
        task.result = continuation.then(this);
        return task;
    }

    public T getResult() {
        return result;
    }
}

最后完整的代码

MyTask.java

public class MyTask<T> {
    private T result;

    public static <TResult> MyTask<TResult> doSth(Callable<TResult> callable) {
        MyTask<TResult> task = new MyTask<>();
        try {
            task.result = callable.call();
        } catch (Exception e) {
            e.printStackTrace();
            task.result = null;
        }
        return task;
    }

    public <TContinueResult> MyTask<TContinueResult> continueDoSth(MyContinuation<T, TContinueResult> continuation) {
        MyTask<TContinueResult> task = new MyTask<>();
        task.result = continuation.then(this);
        return task;
    }

    public T getResult() {
        return result;
    }
}

MyContinuation.java

public interface MyContinuation<TTaskResult, TContinuationResult> {
    TContinuationResult then(MyTask<TTaskResult> task);
}

Test.java

public class Test {
    private static final String TAG = "------------------------> ";

    public void fetchData() {
        MyTask.doSth(new Callable<String>() {
            @Override
            public String call() {
                return callA("a");
            }
        }).continueDoSth(new MyContinuation<String, String>() {
            @Override
            public String then(MyTask<String> task) {
                return callB(task.getResult());
            }
        }).continueDoSth(new MyContinuation<String, String>() {
            @Override
            public String then(MyTask<String> task) {
                return callC(task.getResult());
            }
        }).continueDoSth(new MyContinuation<String, String>() {
            @Override
            public String then(MyTask<String> task) {
                //display
                System.out.println(task.getResult());
                return null;
            }
        });
    }

    private String callA(String param) {
        //fetch A data, then callback
        System.out.println("callA" + TAG + param);
        return param + "b";
    }

    private String callB(String param) {
        //fetch B data, then callback
        System.out.println("callB" + TAG + param);
        return param + "c";
    }

    private String callC(String param) {
        //fetch C data, then callback
        System.out.println("callC" + TAG + param);
        return param + "display";
    }
}

运行截图:


image.png

小结

到这里,算是打开了链接编程的门了。不过,你可能会发现,根据上面写的代码,只能处理单线程的链接编程,至于真正的请求接口,肯定是要开个线程异步请求,而最后的展示又是要在UI线程执行的,所以还要涉及到线程相关的知识,有兴趣的同学自行研究。


补充多线程链式编程的代码

就拿异步请求接口的情况举例吧。
我们期望在编写代码逻辑的时候,也可以像下面一样,链式编程:

    public void fetchData(final String param) {
        MyTask.callInBackground(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return request(param);
            }
        }).continueWith(new MyContinuation<String, String>() {
            @Override
            public String then(MyTask<String> task) {
                display(task.getResult());
                return null;
            }
        }, MyTask.uiExecutor);
    }

    private String request(String param) throws InterruptedException {
        L.info("request start...");
        Thread.sleep(5000);
        L.info("request complete...");
        return "\n" + "request param: " + param + "\n"
                + "response data: " + "this is returned json data";
    }

    private void display(String content) {
        L.info("display start...");
        L.info("content: " + content);
    }

因为接口请求这种耗时操作不能在主线程里做,所以调用接口需要在异步线程里做,所以需要在MyTask类里定义一个方法public static <TResult> MyTask<TResult> callInBackground(final Callable<TResult> callable)

为了让这个方法在异步线程里执行,并且执行完之后下一步能在主线程里更新UI,我们还需要得到主线程。方便起见,我们在MyTask类里分别定义出一个异步线程和一个UI线程:

    public static Executor backgroundExecutor = Executors.newFixedThreadPool(5);
    public static Executor uiExecutor = new UiExecutor();

UIExecutor.java

public class UiExecutor implements Executor {
    @Override
    public void execute(Runnable command) {
        new Handler(Looper.getMainLooper()).post(command);
    }
}

接着编写callInBackground方法和continueWith方法

    public static <TResult> MyTask<TResult> callInBackground(final Callable<TResult> callable) {
        final MyTask<TResult> task = new MyTask<>();
        backgroundExecutor.execute(new Runnable() {
            @Override
            public void run() {
                L.info("callInBackground run...");
                try {
                    task.result = callable.call();
                } catch (Exception e) {
                    e.printStackTrace();
                    L.error("callInBackground exception: " + e.getMessage());
                }
                L.info("callInBackground complete...");
            }
        });
        return task;
    }

    public <TContinueResult> MyTask<TContinueResult> continueWith(final MyContinuation<T, TContinueResult> continuation, Executor executor) {
        final MyTask<TContinueResult> task = new MyTask<>();
        executor.execute(new Runnable() {
            @Override
            public void run() {
                L.info("continueWith run...");
                task.result = continuation.then(thisTask);
                L.info("continueWith complete...");
            }
        });
        return task;
    }

运行一下,看到结果 :


image.png

可以看到,continueWith里面拿到的请求数据为空。可是我们明明在request(String param)方法里返回了数据的呀,为什么拿不到呢?

其实这就是因为异步请求接口的时候,请求和返回都是有耗时操作,如果没有相应的同步机制,那么在UI线程的代码是不会等异步线程的请求结果回来之后再执行的。

所以,在这里需要加个同步机制,在callInBackground方法里的异步操作没执行完之前,不执行continueWith方法。这里就需要用到synchronized关键字了。

修改MyTask类,给其添加一下Object锁

private static final Object lock = new Object();

再修改callInBackground方法和continueWith方法

    public static <TResult> MyTask<TResult> callInBackground(final Callable<TResult> callable) {
        final MyTask<TResult> task = new MyTask<>();
        backgroundExecutor.execute(new Runnable() {
            @Override
            public void run() {
                L.info("callInBackground run...");
                try {
                    synchronized (lock) {
                        task.result = callable.call();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    L.error("callInBackground exception: " + e.getMessage());
                }
                L.info("callInBackground complete...");
            }
        });
        return task;
    }

    public <TContinueResult> MyTask<TContinueResult> continueWith(final MyContinuation<T, TContinueResult> continuation, Executor executor) {
        final MyTask<TContinueResult> task = new MyTask<>();
        executor.execute(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    L.info("continueWith run...");
                    task.result = continuation.then(thisTask);
                    L.info("continueWith complete...");
                }
            }
        });
        return task;
    }

其实只是在callInBackground方法里执行task.result = callable.call();时给其加一个锁,并且在continueWith方法执行task.result = continuation.then(thisTask);时为其加锁。
在这种情况下,代码执行到task.result = continuation.then(thisTask);时就会等task.result = callable.call();执行完释放锁之后才会执行。

再次运行


image.png

可以看到,开始请求之后,过了5秒,请求完成,才开始执行continueWith run里的面的display,展示了请求拿到的数据。流程符合我们的预期,完成!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容