【9.3.1 回调】中,介绍了回调和好莱坞原则
(Java中)回调与通常的(遵循OCP 代码的)非回调代码,使用的技术不过是动态绑定/多态。
回调与通常的非回调代码,从类图和其自身代码上,没有区别。
之所以需要回调callback、隐式调用Implicit invocation(某些软件架构的作者使用的术语),是因为分层结构的一条线的存在。
假定下层有IClient、Server:
package Lower;
@FunctionalInterface
public interface IClient {
public void callback(int i);// 参数为底层上传的数据
}
和
package Lower;
public class Server {
private IClient whoCallMe; //必须获得一个IXxx的引用,由构造器的参数提供
public Server(IClient event) {
whoCallMe = event;
}
public void copy() {
for(int i=0;i<100;i++){
if (i%10 == 0) {//在适当的时机调用回调
whoCallMe.callback(i/10);
}
}
System.out.println("copy() over");
}
}上层Client需要更新进度条——显示复制任务完成的进度时,需要按照下层接口IClient定义的方法callback,给出自己的实现。
package Upper;
import Lower.*;
public class Client implements IClient {
public void call() {
new Server(this).copy();//传递this
}
//下层调用时传回一些数据。
@Override
public void callback(int i) {
System.out.println("Upper:" + i + "0%");
}
public static void main(String[] a) {
//Server
new Client().call();
}
}一个回调函数/方法(简称回调/ callback)是上层模块实现的,将被下层模块(反过来)调用的方法。
3. 回调的实现
package Lower;
import java.util.List;
import java.util.ArrayList;
public class Server2 {
private List listeners = new ArrayList<>();//电话簿
public void register(IClient listener) {//监听器注册
listeners.add(listener);
}
public void copy() {
for (IClient x : listeners) {
for (int i = 0; i <= 100; i++) { //在适当的时机调用回调
if(i % 20 == 0) x.callback(i/10);// 通知所有已登记的演员
}
}
System.out.println("copy() over");
}
}
当下层模块状态发生某些变化时——通常由操作系统或JVM捕捉这种状态变化并调用回调函数,程序员最关心的是上层模块如何提供回调的方法体。最理想的方式是在注册时直接给出代码,如伪代码:
s.register(λi.(操作i)) //λ表达式
事实上,封装代码的callback(int)方法的方法名不需要存在(只需要参数和对参数的处理代码),更不用说封装callback(int)方法的类和对象。
还记得冯?诺依曼的存储-程序概念吗?可执行代码也被储存在内存中。从提供回调的方法体角度,在编程领域,
★回调通常指可以被作为参数传递给其他代码的可执行代码块,或者一个可执行代码的引用。
如果能够将可执行代码封装成方法如foo(),而方法名foo又可以作为其他方法的参数,则可以register(foo)实现回调。在JavaScript, Perl和 PHP中可以如此实现。
如果能够操作可执行代码的内存地址即函数指针(function pointers) 则可以像C或C++那样实现回调。
Java8的λ表达式,终于完成了回调的原意——代码的参数化,即doSth( foo )按照统一的形式,随着foo的不同使得doSth不同。
package Upper;
import Lower.*;
public class Client implements IClient {
public void call() {
new Server(this).copy();//传递this
}
//下层调用时传回一些数据。
@Override
public void callback(int i) {
System.out.println("Upper:" + i + "0%");
}
public static void main(String[] a) {
//Server2
// Server2 s =new Server2();
// s.register( new Client());
// s.register( new Client());
// s. copy();//这里由上层模块触发事件的发生
//3 λ表达式Vs. Java匿名类(参见9.4.5节)
Server2 s =new Server2();
IClient listener1=(i)->{System.out.println("+" + i + "0%");};
s.register(listener1);
s.register((i)->{System.out.println("++" + i + "0%");});
s.register(new IClient(){
@Override public void callback(int i){
System.out.println("==" + i + "0%");
}
});
s. copy();
}
}
为了方便地使用Lambda表达式取代匿名类,Java8新引入了概念:函数接口(functional interface),即仅仅显式声明了一个自己的抽象方法的接口(可以用@FunctionalInterface标注)。
IClient listener1=(i)->{System.out.println("+" + i + "0%");};
λ表达式的类型,叫做“目标类型(target type)”,必须是函数接口。上面的赋值语句,将λ表达式——事实上是函数接口的实现类的引用(也可以理解为像C或C++那样的函数指针)赋值给函数接口。
当然,有了锤子,满世界就有太多的钉子。有的的确是钉子,有的则被当成了钉子。