(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<IClient> 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++那样的函数指针)赋值给函数接口。
当然,有了锤子,满世界就有太多的钉子。有的的确是钉子,有的则被当成了钉子。