您的位置:首页 > 编程语言 > Java开发

《编程导论(Java)·9.3.1Java回调》1

2014-09-22 20:43 447 查看
什么是好莱坞原则中,yqj2065大话连篇,木有代码。这里补充一点代码。

9.3 事件驱动编程

前面所有的程序,都按照程序员预订的流程执行,即使是每次执行过程都不尽相同的多线程程序。即将介绍的事件驱动程序(event-driven program)则不同:当一个GUI程序启动后,JVM显示最初的界面图形(一个applet或窗口,以及各种组件的形状),然后处于无限循环的等待状态。一旦用户的某一个动作(user action)如单击按钮或鼠标移动发生时,JVM经过一系列底层运作找到并执行对应的代码。

最早的Java事件响应机制是层次事件模型,一种笨拙的、非面向对象的模型;Java1.1发布了非常优秀的基于委托的模型(delegation-based model),简称委托模型。正如[1.1.3面向对象]所介绍的,委托是面向对象程序的基本特征。

某种程度上,对象技术的兴旺正是源于事件驱动编程的需求压力:软件开发必须有一套方便的图形界面组件和对各种组件的事件进行响应的机制

9.3.1 回调

回调机制在事件驱动程序/GUI编程、Java远程方法调用(Remote MethodInvocation, RMI)等领域被广泛采用,applet的生命周期、绘制方法都是回调。回调机制是理解委托事件模型的基础。

1. 回调的示例

软件开发中分层是常用的策略,例如管理信息系统常用的逻辑三层结构——表现层、业务逻辑层和数据访问层。分层架构的主要原则是:除公共模块可被各层调用外,一个层的模块只依赖于同一层或下层的模块【单向依赖原则】。这样使得系统的代码具有好的可维护性。

由此,下层模块不得直接调用上层中某个类的方法/接口。然而在编程实践中,下层模块常常希望反过来调用上层的方法(以便向上层模块传递数据)。如图9-11所示,上层模块Client调用了下层Server的copy()方法。假设上层需要更新进度条——显示复制任务完成的进度,显然上层并不清楚复制的进度而只有下层的Server才知道。这时下层模块如何将进度数据传递给上层的Client呢?

一种简单的方式:Server将进度数据保存在一个成员变量x中,并提供getX()。这样就需要Client时时刻刻地查询该数据。如同现实生活中打的士到某地,从上车的第一秒开始,时刻或每隔5秒问司机到了地方没有,也太烦人了。

另一种方式则是通知或使用回调。
补充:

1.轮询

上层模块获得进度数据的方式:轮询和通知。通知的代码见回调与Java8的λ表达式

轮询——不停地查询。此时,下层模块Server0将进度数据保存在一个成员变量x中,并提供getX()。Client通过轮询访问该数据。

轮询方式下,一个线程中Server0努力的复制,主线程则在while(true)中随时随地的查询数据,直到复制进度数据为100才结束。

package API.event.lower;//意味底层模块所在的包
public class Server0{
    private int x;//复制工作的进度
    public int getX(){ return x;    }
    public void copy() {
        while(x<100){
            try{
                Thread.sleep(10);
                x++;
            }catch(InterruptedException e){}
        } 
    }
}

package API.event;//意味上层模块所在的包
import API.event.lower.Server0;
/**
 * 补充:上层模块获得进度数据的方式:轮询和通知。
 * 这里演示轮询
 */
public class Client0{
    private Server0 server =new Server0();
    public void call() {  server.copy();  }
    
    public static void test() throws InterruptedException{
        Client0 upper =new Client0();
        Runnable r = ()->upper.call();
        new Thread(r).start(); 
        System.out.print("进度:");
        int x2 = 0;
        while(true){
            int x1 = upper.server.getX();///10
            Thread.sleep((int)(Math.random()*100));
            if(x2 >=100){
                System.out.println();
                break;
            }
            if (x2 != x1) {                
                System.out.print(x1+"% ");
                x2 = x1;
            }
        }
    }  
}


下面是两次测试的结果。

进度:3% 5% 9% 16% 21%23% 29% 31% 38% 48% 57% 63% 70% 76% 82% 85% 87% 88% 90% 91% 97% 100%

进度:1% 6% 12% 16% 24%31% 39% 47% 49% 54% 61% 64% 67% 77% 83% 90% 100%

轮询方式的优点:上层模块能够随时随地的了解所需的数据(不会被下级欺瞒),依赖关系和方法调用关系都很明确;下层模块代码简洁。缺点:上级时刻盯着下级干活,比较讨嫌;获得数据时执行太多的调用(特别是在网络编程如远程方法调用时,这是不能够容忍的);

2. 回调与多态

讨论回调、好莱坞原则时,都强调了其应用场景:分层架构。那么在非分层架构中,这些思路和技术难道不可以使用吗?如果能够使用,回调、好莱坞原则的概念是否应该推广到更一般的场合——普通类之间的设计?

答案是:在同层中,程序员依然可以使用该技术,它也是解决类之间相互依赖的重要手段。之所以强调其应用场景是分层架构,因为分层强制性地要求依赖的单调性,因而不得不使用回调、好莱坞原则 以达到反向调用的目标。程序员不得不在下层(或基础设施/框架中)定义一个Client的父类型IClient,它定义了回调callback(int)的接口。

如果不能够强制性地要求依赖的单调性,那么同一层中,回调与通常的多态,从类图和其自身代码上,没有区别。



当然,如果同一层中出现图0-5所示的循环依赖,程序员“可以”(而不是“不得不”) 定义一个B的父类型IB,这样就可以将A与B的循环依赖修改为单向依赖;然而对以程序员而言,是否修改为单向依赖是可选的。

虽然观察者模式可以作为回调、好莱坞原则的同义词。但是,回调、好莱坞原则更强调分层架构,观察者模式、λ表达式[b]更一般地用于同层或分层的各种情况。[/b]

一个回调函数/方法(简称回调/callback)是上层模块实现的,将被下层模块(反过来)执行的方法。





内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: