一, 什么是回调函数(callback)
假如类A有1个方法a(), 但是它不知道什么时候调用这个方法a().
而类B告诉类A, 我知道什么时候调用. 那么类A就把自己交给类B(作为类B的一个成员), 当时机适合时, 类B会调用类A的a()方法.
那么a() 就叫回调(callback)方法.
可能我上面的解释不够好,
记住关键两点就得了:
1. 类A 要成为类B的一个成员, java中是通过接口(interface)来实现的.
2. a()的执行由类B决定, 但是执行的是类A的方法a()
二, 什么要使用回调方法.
是啊, 为什么不直接使用类A来调用a()呢?
实际上这个也涉及层次的问题.
实际上, 类B可以调用类A的方法a(), 但是有时也可以调用其他比如类c的方法c(), 只要类c实现了接口, 替换成类B的成员.
也就是说, 客户端只需要跟类B打交道, 无需知道实际执行的哪个类的哪个方法.
比如一个间公司有1个快递部门(类B), 所有员工都可以找这个快递部门寄快递, 但是实际上这个部门是用顺丰(类A)来快递的.
但是当顺丰快递有问题时, 类B也可以找另一间快递公司,比如EMS(类C)来实现.
那么公司员工就不必与真正的快递公司打交道, 只需把物件交给类B去处理就ok了. 也就是这个过程是动态的. 甚至也可以找另一件公司来实现(类D), 只需要类D实现快递接口.
三, 基于上面快递的一个具体例子
现在我们就基于上面的例子写一段代码.
3.1 UML图
可以见上面有5中角色
分别是:
员工: Employee
快递部门: ExDepartment
回调(快递)接口: Callbackable
具体快递公司: SFExpress & EMS
下面是具体代码:
3.2 Callbackable
这个接口只需要定义1个回调方法method()
public interface Callbackable {
public void method();
}
3.3 ExDepartment
同样地, 快递部门类必须预留1个接口成员.
它可以选择1个实现了Callbackable接口的对象作为自己的成员,
public class ExDepartment {
private Callbackable exCompany;
public void setExCompany(Callbackable exCompany) {
this.exCompany = exCompany;
}
public ExDepartment(Callbackable exCompany){
this.setExCompany(exCompany);
}
public void sendGoods(){
exCompany.method();
}
}
这个部门有1个快递方法sendGoods(), 但是实际上是调用这个快递公司成员的回调方法.method();
3.4 SFExpress
具体的快递公司类顺丰, 必须实现Callbackable接口,
并重写里面回调方法.
public class SFExpress implements Callbackable{
public void SFSendGoods(){
System.out.println("Sent goods by SF!");
}
@Override
public void method() {
// TODO Auto-generated method stub
this.SFSendGoods();
}
}
3.5 EMS
同上
public class EMS implements Callbackable {
public void EMSSendGoods(){
System.out.println("Sent GOods by EMS");
}
@Override
public void method() {
// TODO Auto-generated method stub
this.EMSSendGoods();
}
}
3.6 Employee
员工类, 无需知道具体快递公司的存在, 只需要跟快递部门打交道就ok了
public class Employee {
public void sendGoods(ExDepartment exd){
exd.sendGoods();
}
}
3.7 客户端代码和输出:
ExDepartment exd = new ExDepartment(new SFExpress());
Employee jack = new Employee();
jack.sendGoods(exd);
exd.setExCompany(new EMS());
jack.sendGoods(exd);
exd.setExCompany(new Callbackable() {
@Override
public void method(){
System.out.println("Sent goods by Jimmy!");
}
});
jack.sendGoods(exd);
输出:
Sent goods by SF!
Sent GOods by EMS
Sent goods by Jimmy!
可以见到, 员工寄了3次快递, 第一次是顺丰, 第一次是EMS, 但是第三次是某个人(可能两个快递公司都放假, 随便找个人来送)
这个模型的好处时,
无论有什么具体快递类的修改, 只需要修改快递类本身.
而增加快递公司类, 只需让那个类实现回调接口, 而员工类和快递部门类都无需修改的.
四, java的控件事件方法也是用回调来实现的.
上面的代码有这么一段:
exd.setExCompany(new Callbackable() {
@Override
public void method(){
System.out.println("Sent goods by Jimmy!");
}
});
上面用到了内部类的方法.
是不是觉得有d面善.
这种写法跟java的button事件定义优点类似.
下面是新建1个button的例子:
JButton btnNewButton = new JButton("New button");
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
}
});
其中btnNewButton就相当于上面例子的快递部门ExDepartment
而ActionListener实际上就是1个回调接口. (在这里也叫监听器)
而actionPerFormed就是1个回调方法.
没错, java界面(awt/swt)的控件事件就是通过回调来实现的.
你们想想, 一个button, 按下去做什么不是固定的.
有的button触发 行为a(), 有些触发行为b()
所以, 我们让行为b()写成回调方法, 也就是所让它所需的类实现 回调接口ActionListener().
这样, 就可以很灵活地为每1个button定义不同的触发行为了!