转载请注明出处

作者:晓渡
文章地址:https://greatestrabit.github.io/2016/03/29/callback/

1.字面意义上的回调

字面意思上理解回调,就是A调用B,B回过头来再调用A,即是回调.既然是这样,当然就要求A中有B,B中有A.如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class A {
/**
* 提出问题
* @author xiaodu.email@gmail.com
* @param b
* @param question
*/

public void ask(final B b, final String question) {
b.answer(this, question);
}

/**
* 处理结果
* @author xiaodu.email@gmail.com
* @param answer
*/

public void processResult(final String answer) {
System.out.println(answer);
}
}

class B {
/**
* 计算结果
* @author xiaodu.email@gmail.com
* @param a
* @param question
*/

public void answer(final A a, final String question) {
if (question.equals("What is the answer to life, the universe and everything?")) {
a.processResult("42");
}
}
}

/**
* 相互调用
* @author xiaodu.email@gmail.com
*
*/

public class SyncObjectCallback {
public static void main(final String[] args) {
B b = new B();
A a = new A();

a.ask(b, "What is the answer to life, the universe and everything?");
}
}

2.面向对象的回调

上面的写法中,B的对象只在方法中被传递了.实际上,这个B对象后来又调用了A中的方法,它的作用应该不止局限在一个方法中,而应该是A的一个部分.也就是,上面的写法不够”面向对象”,让我们来改造一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class A {
private final B b;

public A(final B b) {
this.b = b;
}

public void ask(final String question) {
this.b.answer(this, question);
}

public void processResult(final String answer) {
System.out.println(answer);
}
}

class B {
public void answer(final A a, final String question) {
if (question.equals("What is the answer to life, the universe and everything?")) {
a.processResult("42");
}
}
}

/**
* 面向对象的相互调用
* @author xiaodu.email@gmail.com
*
*/

public class SyncOOCallback {
public static void main(final String[] args) {
B b = new B();
A a = new A(b);
a.ask("What is the answer to life, the universe and everything?");
}
}

3.面向接口的回调

上面的两个例子,估计没人会承认也是回调吧.因为并没什么卵用.不过这个流程对于理解回调是很重要的.其实回调真正有用的地方,在于它的”预测”能力.
我们扩展想象一下.假设上面例子中的B,为A提供了很多服务之后突然觉醒,想为更多的对象提供服务,这样一来,B就变成了Server.而且还要制定规则.规则是什么呢,就是要Server提供服务可以,对方一定要有一个recvAnswer接口供Server调用才行,这样Server才能把结果传回给Client.具体如何制定规则呢?通过Interface.如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/**
* 发出请求着需要实现的接口,要实现处理结果的方法
* @author xiaodu.email@gmail.com
*
*/

public interface IClient {
void recvAnswer(String answer);
}

/**
* 响应请求者,即提供服务者
* @author xiaodu.email@gmail.com
*
*/

public class Server {
public void answer(final IClient client, final String question) {
if (question.equals("What is the answer to life, the universe and everything?")) {
calclating();
client.recvAnswer("42");
}
}

private void calclating() {
try {
Thread.sleep(new Random().nextInt(5000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

/**
* 发出请求者,同时要处理请求结果
* @author xiaodu.email@gmail.com
*
*/

public class ClientSync implements IClient {
private final Server server;

public ClientSync(final Server server) {
this.server = server;
}

public void ask(final String question) {
this.server.answer(this, question);
}

@Override
public void recvAnswer(final String answer) {
System.out.println(answer);
}
}

/**
* 面向接口的同步回调
* @author xiaodu.email@gmail.com
*
*/

public class SyncInterfaceCallback {
/**
* 使用内部类来实现的方式
* @author xiaodu.email@gmail.com
*/

private static void innerMain() {
Server server = new Server();
server.answer(new IClient() {
@Override
public void recvAnswer(final String answer) {
System.out.println(answer);
}
}, "What is the answer to life, the universe and everything?");
}

public static void main(final String[] args) {
Server server = new Server();
ClientSync client = new ClientSync(server);
client.ask("What is the answer to life, the universe and everything?");

innerMain();
}
}

注意,接口IClient实际上应该是属于Server端的,它是由Server制定的,需要Client来实现的接口,虽然看上去它跟Client很近.
为什么说有”预测”能力呢?想象另一个场景.Server现在是一个底层服务,这个底层服务知道迟早有一天会有高层服务来讨要数据,但是数据如何向上传递呢?底层可以承诺,只有你实现IClient接口,我就会调用其中的recvAnswer方法,把数据传上来.现在底层也可以调用高层的方法,算是有”预测”能力吧?

4.异步回调

上面的调用都是同步的.假设Server计算结果需要较长的时间,你一定希望它能在一个单独的线程中被执行,这是就可以把ask方法的调用用线程包装一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class ClientAsync implements IClient {
private final Server server;

public ClientAsync(final Server server) {
this.server = server;
}

/**
* 在线程中发出请求
* @author xiaodu.email@gmail.com
* @param question
*/

public void ask(final String question) {
new Thread(new Runnable() {
@Override
public void run() {
server.answer(ClientAsync.this, question);
}
}).start();
}

@Override
public void recvAnswer(final String answer) {
System.out.println(answer);
}
}

/**
* 基于接口的异步回调,每次建立新的线程
* @author xiaodu.email@gmail.com
*
*/

public class AsyncInterfaceCallback {
/**
* 使用内部类的实现方式,此处可见回调地狱
* @author xiaodu.email@gmail.com
*/

private static void innerMain() {
Server server = new Server();

new Thread(new Runnable() {
@Override
public void run() {
server.answer(new IClient() {
@Override
public void recvAnswer(final String answer) {
System.out.println(answer);
}
}, "What is the answer to life, the universe and everything?");
}
}).start();
System.out.println("asked ! waiting for the answer...");
}

public static void main(final String[] args) {
Server server = new Server();
ClientAsync client = new ClientAsync(server);
client.ask("What is the answer to life, the universe and everything?");
System.out.println("asked ! waiting for the answer...");

innerMain();
}
}

5.线程池异步回调

每次建立新的线程耗费资源巨大,为了重用线程,使用线程池管理异步调用,这时候就要求Client不仅要实现IClient接口,还要同时是一个任务,才能被线程池执行,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* 专门用来执行请求的任务,供线程池调用
* @author xiaodu.email@gmail.com
*
*/

public class ClientRunnable implements IClient, Runnable {
private final Server server;
private final String question;
private final int id;

public ClientRunnable(final Server server, final String question, final int id) {
this.server = server;
this.question = question;
this.id = id;
}

@Override
public void recvAnswer(final String answer) {
System.out.println("clinet " + this.id + " got answer: " + answer);
}

@Override
public void run() {
server.answer(ClientRunnable.this, this.question);
}
}

/**
* 基于线程池的异步回调
* @author xiaodu.email@gmail.com
*
*/

public class ThreadpoolCallback {
public static void main(final String[] args) {
ExecutorService es = Executors.newCachedThreadPool();

Server server = new Server();

for (int i = 0; i < 100; i++) {
ClientRunnable cr = new ClientRunnable(server, "What is the answer to life, the universe and everything?",
i);
es.execute(cr);
System.out.println("client " + i + " asked !");
}

es.shutdown();
}
}

至此,我们就实现了线程池异步回调.

完整源码请移步:github