一个java类对回调怎么写_Java如何实现一个回调地狱(Callback Hell)?

本文详细介绍了回调函数的概念,通过一个简单的Java回调示例解释了回调的工作原理,展示了如何创建一个回调地狱(Callback Hell),并讨论了回调地狱的优缺点。此外,还对比了回调与Future的区别,并提出了解决回调地狱的方案,如使用RxJava和Reactor等反应式编程框架。
摘要由CSDN通过智能技术生成

对于回调地狱(Callback hell),想必大家都不陌生,尤其对于前端的朋友,当然前端的朋友通过各种办法去避免回调地狱,比如Promise。但是对于后端的朋友,尤其在RxJava、Reactor等反应式编程框架兴起之后,对于回调地狱只是听得多,但是见得的少。

为了更好了解回调地狱Callback hell问题在哪,我们首先需要学会怎么写出一个回调地狱。在之前,我们得知道什么是回调函数。

本文将包含:

什么是回调

回调的优势

回调地狱是什么

为什么会出现回调地狱

回调和Future有什么区别

如何解决回调地狱

我们今天从最开始讲起,先讲讲什么是回调函数。

什么是回调函数?

在百度百科上,是这么说的:

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。 回调是任何一个被以方法为其第一个参数的其它方法的调用的方法。很多时候,回调是一个当某些事件发生时被调用的方法。

什么?不好理解?确实很难理解,并且这段解释还有指针云云,对于java用户实在是不友好。

给大家举个例子,供大家参考,也欢迎批评指正:

回调:调用方在调用被调方后,被调方还将结果反馈给调用方。(A调用B,B完成后,将结果反馈给A) 举个例子:老板安排员工一项工作,员工去完成。员工完成工作后,给老板反馈工作结果。这个过程就叫回调。

bd3fcb7a7708a9029435e5b3097ec4c9.png

这下容易理解很多了吧!Talk is cheap, Show me the code! 好,我们就用这个写一个简单的例子。

回调的例子

Callback接口

首先,我们先写一个如下的Callback接口,接口只包含一个方法,用于callback操作。

/**

* @author yangzijing

*/

public interface Callback {

/**

* 具体实现

* @param t

*/

public void callback(T t);

}

Boss类

老板是被反馈的对象,于是需要实现Callback这个接口,重载callback方法;对于老板具体要干什么,当然是做大生意,于是有了makeBigDeals方法;老板当然不能是光杆司令,他需要一个员工,我们再构造方法里给他添加一个员工Worker,稍后我们来实现Worker类。

public class Boss implements Callback {

private Worker worker;

public Boss(Worker worker) {

this.worker = worker;

}

@Override

public void callback(String s) {

}

public void makeBigDeals(final String someDetail) {

worker.work(someDetail);

}

}

Worker类

员工类,很简单,出入一个工作,完成就好了,返回结果即可。但是如何完成回调?

public class Worker {

public String work(String someWork) {

return 'result';

}

}

我们很容易想到就是这个思路,非常符合思维的逻辑,但是在回调中,我们需要做一些改变。

让代码回调起来

对于员工来说,需要知道两点,谁是老板,需要干啥。于是,输入两个参数,分别是老板和工作内容。具体内容分两步,首先完成任务,之后则是汇报给老板。

public class Worker {

public void work(Callback boss, String someWork) {

String result = someWork + 'is done!'; // 做一些具体的处理

boss.callback(result); // 反馈结果给老板

}

}

接下来,我们完成Boss类。在callback方法中,接收到传来的结果,并对结果进行处理,我们这里仅打印出来;在makeBigDeals方法中,老板分配工作,员工去完成,如果完成过程是异步,则是异步调用,如果是同步的,则是同步回调,我们这里采用异步方式。

在新建线程中,我们执行worker.work(Boss.this, someDetail),其中Boss.this即为当前对象,在这里,我们正式完成了回调。

public class Boss implements Callback {

……

@Override

public void callback(String result) { // 参数为worker输出的结果

logger.info("Boss got: {}", result) // 接到完成的结果,并做处理,在这里我们仅打印出来

}

public void makeBigDeals(final String someDetail) {

logger.info("分配工作");

new Thread(() -> worker.work(Boss.this, someDetail)); // 异步完成任务

logger.info("分配完成");

logger.info("老板下班。。");

}

}

回调结果

Show me the result! 好,跑一下代码试一下。

Worker worker = new Worker();

Boss boss = new Boss(worker); // 给老板指派员工

boss.makeBigDeals("coding"); // 老板有一个代码要写

结果如下。在结果中可以看到,老板在分配完工作后就下班了,在下班后,另一个线程通知老板收到反馈"coding is done"。至此,我们完成了异步回调整个过程。

INFO 2019 九月 20 11:30:54,780 [main] - 分配工作

INFO 2019 九月 20 11:30:54,784 [main] - 分配完成

INFO 2019 九月 20 11:30:54,784 [main] - 老板下班。。

INFO 2019 九月 20 11:30:54,787 [Thread-0] - Boss got: coding is done!

我将代码示例传至Github,供大家参考。 callback代码示例

回调的优势

解耦,回调将子过程从主过程中解耦。 对于相同的输入,可能对其有不同的处理方式。在回调函数,我们完成主流程(例如上面的Boss类),对于过程中的子流程(例如上面的Worker类)从主流程中分离出来。对于主流程,我们只关心子过程的输入和输出,输入在上面的例子中即为Worker.work中的参数,而子过程的输出则是主过程的callback方法的参数。

异步回调不会阻塞主线程。上面的例子清晰可以看到,员工没有完成工作之前老板就已经下班,当工作完成后,会通过另一个线程通知老板。老板在这个过程无需等待子过程。

回调地狱

总体设计

我们将上述功能扩展,老板先将工作交给产品经理进行设计;设计完成后,交给程序员完成编码。流程示意如图。

56e7464371b883545bd79abb2b5a0f0e.png

将任务交给产品经理

首先,写一个Callback,内部new一个产品经理的的Worker,在makeBigDeal方法实现主任务,将任务交给产品经理;在重载的callback方法中,获取产品经理的输出。

new Callback() {

private Worker productManager = new Worker();

@Override

public void callback(String s) {

System.out.println("产品经理 output: " + s); // 获取产品经理的输出

}

public void makeBigDeals(String bigDeal) {

System.out.println("Boss将任务交给产品");

new Thread(() -> {

this.productManager.work(this, bigDeal); // 异步调用产品经理处理过程

}).start();

}

}.makeBigDeals("design");

再将产品经理输出交给开发

在拿到产品经理的输出之后,再将输出交给开发。于是我们在再次实现一个Callback接口。同样的,在Callback中,new一个开发的Worker,在coding方法中,调用Worker进行开发;在重载的callback方法中,获取开发处理后的结果。

@Override

public void callback(String s) {

System.out.println("产品经理 output: " + s); // 产品经理的输出

String midResult = s + " coding";

System.out.println("产品经理设计完成,再将任务交给开发");

new Callback() {

private Worker coder = new Worker();

@Override

public void callback(String s) {

System.out.println("result: " + s); // 获取开发后的结果

}

public void coding(String coding) {

new Thread(() -> coder.work(this, coding)).start(); // 调用开发的Worker进行开发

}

}.coding(midResult); // 将产品经理的输出交给开发

}

完整的实现

new Callback() {

private Worker productManager = new Worker();

@Override

public void apply(String s) {

System.out.println("产品经理 output: " + s);

String midResult = s + " coding";

System.out.println("产品经理设计完成,再将任务交给开发");

new Callback() {

private Worker coder = new Worker();

@Override

public void apply(String s) {

System.out.println("result: " + s);

}

public void coding(String coding) {

new Thread(() -> coder.work(this, coding)).start();

}

}.coding(midResult);

}

public void makeBigDeals(String bigDeal) {

System.out.println("Boss将任务交给产品");

new Thread(() -> this.productManager.work(this, bigDeal)).start();

}

}.makeBigDeals("design");

好了,一个简单的回调地狱完成了。Show me the result!

Boss将任务交给产品

产品经理 output: design is done!

产品经理设计完成,再将任务交给开发

result: design is done! coding is done!

回调地狱带来了什么?

到底什么是回调地狱?简单的说,回调地狱就是Callback里面又套了一个Callback,但是如果嵌套层数过多,仿佛掉入地狱,于是有了回调地狱的说法。

优势: 回调地狱给我们带来什么?事实上,回调的代码如同管道一样,接收输入,并将处理后的内容输出至下一步。而回调地狱,则是多个管道连接,形成的一个流程,而各个子流程(管道)相互独立。前端的朋友可能会更熟悉一些,例如Promise.then().then().then(),则是多个处理管道形成的流程。

劣势: 回调的方法虽然将子过程解耦,但是回调代码的可读性降低、复杂性大大增加。

Callback Hell示例:Callback Hell

和Future对比

在上面,我们提到异步回调不会阻塞主线程,那么使用Future也不会阻塞,和异步回调的差别在哪?

我们写一个使用Future来异步调用的示例:

logger.info("分配工作...");

CompletableFuture future = CompletableFuture.supplyAsync(() -> worker.work(someDetail));

logger.info("分配完工作。");

logger.info("老板下班回家了。。。");

logger.info("boss got the feedback from worker: {}", future.get());

在上面的代码,我们可以看到,虽然Worker工作是异步的,但是老板获取工作的结果(future.get())的时候却需要等待,而这个等待的过程是阻塞的。这是回调和Future一个显著的区别。

如何解决

如何解决回调地狱的问题,最常用的就是反应式编程RxJava和Reactor,还有Kotlin的Coroutine协程,OpenJDK搞的Project Loom。其中各有优势,按下不表。

总结

总结一下:

什么是回调。回调是调用方在调用被调方后,被调方还将结果反馈给调用方。(A调用B,B完成后,将结果反馈给A)

回调的优势。1)子过程和主过程解耦。2)异步调用并且不会阻塞主线程。

回调地狱是什么。回调地狱是回调函数多层嵌套,多到看不清=。=

为什么会出现回调地狱。每一个回调像一个管道,接受输出,处理后将结果输出到下一管道。各个管道处理过程独立,多个管道组成整个处理过程。

回调和Future有什么区别。1)两者机制不同;2)Future在等待结果时会阻塞,而回调不会阻塞。

如何解决回调地狱。最常见的则是反应式编程RxJava和Reactor。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值