我们先来看下维基百科的定义:
在计算机程序设计中,回调函数,或简称回调(call-after),是指通过函数参数传递到其它代码的,某一块可执行代码的引用。这一设计允许了底层代码调用在高层定义的子程序。
这种标准的定义,大多数时候说的都比较抽象,下面我们以实际生活中的例子来讲解到底什么是回调函数。
回调函数的用途十分广泛,在各种编程语言里面都有体现,有点类似Spring里面IOC(inversion of control=控制反转)的概念,本身是一个非常简单的概念,看下面的一个例子:
假设一个场景:
老师给学生布置了作业,学生收到作业后开始写作业,写完之后通知老师查看,老师查看之后就可以回家。
回调的概念,在这里面就体现的淋漓尽致,在这里面有两个角色,一个是老师,一个是学生。老师有两个动作,第一个是布置作业,第二个是查看作业。而学生有一个动作是做作业, 那么问题来了,老师并不知道学生何时才能做完作业,所以比较优雅的解决办法是等学生的通知,也就是学生做完之后告诉老师就可以。这就是典型的回调理念。
那么在编程中,该如何体现? 从上面的分析中,可以得出来回调模式是双方互通的,老师给学生布置作业,学生做完通知老师查看作业。 关于回调,这里面还分同步回调和异步回调两种模式:
同步模式:
如果老师在放学后,给学生布置作业,然后一直等待学生完成后,才能回家,那么这种方法就是同步模式。
异步模式:
如果老师在放学后,给学生布置作业,这个时候老师并不想等待学生完成,而是直接就回家了,但告诉学生,如果完成之后发短信通知自己查看。这种方式就是异步的回调模式。
回调模式为了不影响主任务执行,一般会设计成异步任务。下面我们看下在Java中,模拟上面举的例子实现一个简单的回调,包括同步和异步两种模式:
首先,回调的方法我们最好定义成一个接口,这样便于扩展:
/***
*通过接口定义回调函数
*/
public interface CallBack {
//检查作业属于老师的功能,但由学生触发,故称回调
public void checkWork();
}
然后,我们定义老师的角色:
package design_pattern.callback.demo2;
public class Teacher implements CallBack {
private Student student;
public Teacher(Student student) {
this.student = student;
}
/***
* 给学生分配作业
* @param isSync true=同步回调 false=异步回调
* @throws InterruptedException
*/
public void assignWork(boolean isSync) throws InterruptedException {
System.out.println("老师分配作业完成....");
if(isSync){
student.doWrok(this);//同步通知做作业
}else{
student.asynDoWrok(this);//异步通知做作业
}
System.out.println("老师回家了....");
}
@Override
public void checkWork() {
System.out.println("老师收到通知并查看了学生的作业!");
}
}
上面定义的是老师角色,有两个行为,一个是布置作业,一个是检查作业,布置作业里面,在布置作业里面,老师可以选择同步回调还是异步回调。
接着我们看下学生角色如何定义:
public class Student {
public void doWrok(CallBack callBack) throws InterruptedException {
System.out.println("学生开始做作业.....");
TimeUnit.SECONDS.sleep(3);
System.out.println("学生完成作业了,通知老师查看");
callBack.checkWork(); //通知老师查看作业
}
public void asynDoWrok(CallBack callBack) throws InterruptedException {
//通过一个线程来异步的执行任务
Runnable runnable= new Runnable(){
@Override
public void run() {
System.out.println("学生开始做作业.....");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("学生完成作业了,通知老师查看");
callBack.checkWork();
}
};
Thread thread=new Thread(runnable);
thread.start();
}
}
学生角色里面只有一个行为做作业,但这里我提供了两种模式,一个是同步,一个是异步。
最后,我们来测试下:
public class AppMain {
public static void main(String[] args) throws InterruptedException {
Student student=new Student();//学生角色
System.out.println("\n===============同步模式================");
Teacher teacher=new Teacher(student);//老师角色
//同步回调模式,老师给学生布置作业,老师等学生完成之后才能回家
teacher.assignWork(true);
System.out.println("\n===============异步模式================");
//异步回调模式,老师给学生布置作业,布置完成之后就可以回家,学生完成之后会通知老师查看。
teacher.assignWork(false);
}
}
执行结果如下:
===============同步模式================
老师分配作业完成....
学生开始做作业.....
学生完成作业了,通知老师查看
老师收到通知并查看了学生的作业!
老师回家了....
===============异步模式================
老师分配作业完成....
老师回家了....
学生开始做作业.....
学生完成作业了,通知老师查看
老师收到通知并查看了学生的作业!
对于同步和异步两种模式的结果,在上面的输出内容里面可以非常清晰的看出来区别,也体现回调的双通模式。老师角色持有了学生对象的引用,并告诉学生做作业,而同时学生角色,也持有老师角色的引用,可以在自己完成作业后,告诉老师查看作业。
总结:
回调模式,在生活中的例子非常常见,在编程中最常见的就是各种GUI编程里面的按钮点击什么的,通过回调可以将控制权转移,配合上异步模式,可以让系统设计的更加优雅。