什么是回调函数(CallBack)

我们先来看下维基百科的定义:

在计算机程序设计中,回调函数,或简称回调(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编程里面的按钮点击什么的,通过回调可以将控制权转移,配合上异步模式,可以让系统设计的更加优雅。

Top