版权声明:本文为博主原创文章,未经博主同意不得转载。 https://blog.csdn.net/yqj2065/article/details/31441221

接《9.3.1Java回调 ·1》(概念)和《编程导论(Java)·9.3.1回调·2》什么是好莱坞法则

本文改写《回调·3》,由于Java8引入了Lambda Expressions



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

【回调。或隐式调用Implicit invocation(某些软件架构的作者使用的术语)】

样例:上层Client须要更新进度条——显示复制任务完毕的进度时,下层模块Server怎样将进度数据传递给上层的Client呢?

通常有两种解决方式:①轮询;②回调或通知。

【书上的图9-12。在一个包中定义了4个类型,依照分层的要求,应该把代码分别放在不同包中——这里改动了书上的相关内容(包括代码)。

可是我就不方便截图了。

在图9-11中,Client定义的方法callback(int),将被Server这个被调用者反过来调用。请注意图中的分层线,通常下层模块Server不知道上层定义的接口也不应该/能够直接调用上层接口。怎样解决这个小问题呢?能够在公共模块/下层模块中设计一个抽象类或接口如IXxx,定义回调方法的规范。而Server调用公共模块IXxx的抽象方法callback(int)就可以。换言之,Server不能够调用上层模块的方法,那么调用本层的IXxx的方法总是能够的。类型之间的关系如图9-12所看到的。

【不好绘图啊。先这样将就一下】图9-12及以下的例程说明了Java中使用回调的基本结构。

它由上层模块Client、TestCallback和下层被调用者Server和公共模块IClient组成。Client的callback(int) 方法被称为回调。而IClient定义的抽象方法callback(int) 被称为回调接口,大多数情况下,回调接口也简称回调。(书上原文:注意:通常公共模块、上层模块和下层模块均有自己的包。这里简单地将它们全部放在同一个包API.event中,只为了方便地查看相关代码。在实践中。3个模块通常由不同的程序猿编写。

package API.event.lower;
/**
* 通常在公共模块中设计一个抽象类或接口如IClient,定义回调的契约/规范。 * 公共模块通常有自己的包。
* @author yqj2065
* @version 0.2
*/
public interface IClient{
/**
* 回调接口,參数为底层将上传的数据。 * 比如 copy的进度。
*/
public void callback(int i);
} package API.event.lower;
/**
 * 下层Server知道拷贝的进度。
 * copy()在适当的时机调用回调来通知上层。
 * @author yqj2065
 */
public class Server{
    private IClient whoCallMe;//必须获得一个IClient 的引用,由构造器的參数提供
    public Server(IClient listener) {//
        whoCallMe = listener;
    }     public void copy() {
        for(int i=0;i<100;i++){
            //在适当的时机调用回调
            if (i%10 == 0) {
                whoCallMe.callback(i/10);
            }            
        }
        System.out.println("copy() over");
    }
}

上层模块代码例如以下:

package API.event;//意味上层模块所在的包
import API.event.lower.Server;//使用
import API.event.lower.IClient;//继承
/**
* 上层模块Client,是公共模块/下层模块IClient的实现类。
* @author yqj2065
* @version 0.1
*/
public class Client implements IClient{
/**
* 调用下层Server的copy()方法。
* 可是下层必须知道要通知谁(一个IClient的引用)
* 因而在Server(IClient)中传递this。
*/
public void call() {
new Server(this).copy();//传递this
}
/*
* 回调方法。下层模块运行时,传回一些数据。 */
@Override public void callback(int i) {
System.out.println(i+"0%");
}
}//class Client package API.event;
import API.event.lower.Server;//使用
public class TestCallback{    
    public static void test(){
        new Client().call();
    }
    public static void foo(){
        new Server(new Client()).copy();
    }
}

这个例程说明:回调方法是某个上层模块实现的某个功能。可是全部的上层模块都不会直接调用它,设计它的目的就是为了下层模块的反向“调用”。

另外。回调(callback)是一个名词,而非动词(call back)。并不是意味着“我调用你。你调用我”,比如任一上层模块如TestCallback能够调用下层Server的copy(),可是它不提供回调,而由Client提供。

2. 好莱坞法则

前面站在上层模块Client的角度考虑回调,如今站在下层模块Server的角度又一次考虑回调。另外增添一点变化——多个Client的对象或多个IClient的其它实现类对下层Server2对象的某种状态变化感兴趣。

以现实生活中的场景为例:一些男女演员们(Client)都对某导演(Server2)是否拍新片子感兴趣。导演肯定和的士司机一样,不喜欢演员们天天打电话询问。于是导演提供了一个让感兴趣的演员们留下电话号码的接口register(IXxx listener)。演员工会TestCallback2组织大家登记。一旦导演准备拍摄一部新片子(sthHappened())就通知全部已登记的演员。

而对于那些打电话询问的演员,导演告诉他们一条好莱坞法则:"Don't
call me; I'llcall you."

类型之间的关系如图9-13所看到的(略)。对照图9-12及其例程,本例程中Server2并不关心Client是否调用自己——即call()是无关紧要的。这是极其重要的一个细节:Client与Server2之间没有依赖关系。

公共模块IClient、上层模块Client不须要做不论什么变动。其它代码例如以下

package API.event.lower;
import java.util.List;
import java.util.ArrayList; public class Server2{//导演
private List<IClient> listeners = new ArrayList<IClient>();//电话簿
/**
* 监听器注冊
*/
public void register(IClient listener) {
listeners.add(listener);
} public void sthHappened(){//某种状态发生改变
for(IClient x: listeners) {
x.callback(12345);//
}
}
} package API.event;
import API.event.lower.Server2;//使用
/**
 * TestCallback2.java.
 * 上层模块
 * @author yqj2065
 */
class TestCallback2{
    public static void test(){
        Server2 s =new Server2();
        s.register( new Client());
        s.register( new Client());
        s.sthHappened();//这里由上层模块触发事件的发生
    }
}

TestCallback2.test()运行时,首先创建Server2对象s,并将两个Client对象进行注冊,因而s对象的listeners保留两者的引用;当调用sthHappened()时——由上层模块触发事件的情形在网络程序中会出现,s先后调用两个IClient对象的callback(int)方法,IClient对象运行回调对sthHappened事件做出回应。请注意:GUI等程序中。一般不会出现上层模块触发事件发生,这里的作法不过为了方便演示。

本例程的关键在于下层模块状态发生某些变化时——某些事件发生时,能够找到上层模块相应的处理代码(回调函数)。而这一点正是Java托付事件模型的核心。

3. 回调的实现

当下层模块状态发生某些变化时——通常由操作系统或JVM捕捉这样的状态变化并调用回调函数。程序猿最关心的是上层模块怎样提供回调的方法体。最理想的方式是在注冊时直接给出代码。如伪代码:

s.register(λi.(操作i)) //λ表达式

其实,封装代码的callback(int)方法的方法名不须要存在(只须要參数和对參数的处理代码),更不用说封装callback(int)方法的类和对象。

还记得冯•诺依曼的存储-程序概念吗?可运行代码也被储存在内存中。从提供回调的方法体角度,在编程领域。
★回调通常指能够被作为參数传递给其它代码的可运行代码块,或者一个可运行代码的引用。
假设能够将可运行代码封装成方法如foo(),而方法名foo又能够作为其它方法的參数,则能够register(foo)实现回调。在JavaScript, Perl和 PHP中能够如此实现。

假设能够操作可运行代码的内存地址即函数指针(function pointers) 则能够像C或C++那样实现回调。

假设习惯面向对象的传统方式。方法必须由类封装,那么Java封装抽象方法callback(int)的IClient和实现类Client提供了一个清晰可是笨重的实现方式。

Java匿名类(參见9.4.5节)则对此稍加改进。

如今,Java8的λ表达式,最终完毕了回调的原意——代码的參数化。即doSth( foo )依照统一的形式,对传入foo进行处理。

(书上列举的C的函数指针(function pointers) 和C#1.0的托付(delegate)及其简化——C# 2.0的匿名方法或C# 3.0的λ表达式,能够删除)

package API.event;
import API.event.lower.Server2;//使用
import API.event.lower.IClient;//使用
/**
 * TestCallback2.java.
 * 上层模块
 * @author yqj2065
 */
class TestCallback2{
    public static void test(){
        Server2 s =new Server2();
        s.register( new Client());
        s.register( new Client());
        s.sthHappened();//这里由上层模块触发事件的发生
    }
    
    public static void test2() {        
        // λ表达式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.sthHappened();        
    }
}

为了方便地使用Lambda表达式代替匿名类。Java8新引入了概念:函数接口(functional interface),即只显式声明了一个自己的抽象方法的接口(能够用@FunctionalInterface标注)。

IClient listener1=(i)->{System.out.println("+" + i + "0%");};

λ表达式的类型,叫做“目标类型(target type)”,必须是函数接口。上面的赋值语句。将λ表达式——其实是函数接口的实现类的引用(也能够理解为像C或C++那样的函数指针)赋值给函数接口。

BTW:

回调机制——使用回调、好莱坞法则设计程序/框架的风格。或者说,下层模块lower.Server调用同层(或更下层的)lower.IClient的方法m()。而运行了上层模块(lower.IClient的子类型)Client的m()方法的编程方式。通常解释为动词(call back)。

★上层模块的观点:回调以通知代替轮询

既然有“通知”,那么自然地包括了观察者模式。相应于上层模块的。是观察者observer/被通知者。相应于下层模块的。就是源source或目标/subject。

练习9-22:将Server2更名为Subject/目标或Source/事件源,Client更名为Observer/观察者或Listener,网络学习:观察者/Observer设计模式。

它以好莱坞法则作为内在准则。
★下层模块的观点:好莱坞法则(Hollywood Principle):"Don't call me; I'll call you."。

(上层不要轮询我,)我通知你。

★下层模块的观点:某些事件发生时,能够找到上层模块提供的处理代码 (回调函数)。

这就使得编写上层模块的程序猿有了新的体验——填空式编程。这就有了库与框架的差别——上层模块的程序猿直接调用的。属于库函数。要求上层模块的程序猿提供的回调函数的,属于框架。

假设我们通吃上层和下层。设计框架时使用回调机制;假设让应用程序猿填空。能够告诉他们一个术语——控制反转


《编程导论(Java)·9.3.1回调·3》回调的实现的更多相关文章

  1. 《编程导论(Java)&#183;2.1.3改写(override)》

    <编程导论(Java)·2.1.3改写(override)>,收集override内容. 方法改写(method overriding)是指对于父类定义的一个实例方法,同意子类提供自己的实 ...

  2. 《编程导论(Java)&#183;1.4.1 范式》

    这个楼主,是我的学生么?2013年写的! 嗯."编程范式或许是学习不论什么一门编程语言时要理解的最重要的术语".这句话早在2005年出版<Java程序设计>(宋中山,严 ...

  3. 《编程导论(Java)&#183;1.1.2 颠倒的世界(柏拉图法则)》

    假设你读<编程导论(Java)·1.1.2 颠倒的世界(柏拉图法则)>感到无趣,请尝试评价这个段子. 3. Classes Classes drive me crazy. That mig ...

  4. 《编程导论(Java)&#183;3.3.2 按值传递语义》

    不要受<Java编程思想>的影响,计算机科学中的术语--按引用传递(pass-by-reference).不要搞成自说自话的个人用语. 这些术语也不是专门针对Java的,你不应该从某一本J ...

  5. 《编程导论(Java)&#183;3.2.4 循环语句》

    本文全然复制<编程导论(Java)·3.2.4 循环语句>的内容.除[]中的说明文字.请阅读和比較其它编程教材. 我知道.假设我是一个刚開始学习的人,<编程导论(Java)>非 ...

  6. 33.JAVA编程思想——JAVA IO File类

    33.JAVA编程思想--JAVA IO File类 RandomAccessFile用于包括了已知长度记录的文件.以便我们能用 seek()从一条记录移至还有一条:然后读取或改动那些记录. 各记录的 ...

  7. 为什么函数式编程在Java中很危险?

    摘要:函数式编程这个不温不火的语言由来已久.有人说,这一年它会很火,尽管它很难,这也正是你需要学习的理由.那么,为什么函数式编程在Java中很危险呢?也许这个疑问普遍存在于很多程序员的脑中,作者Ell ...

  8. 《编程简介(Java) &#183;10.3递归思想》

    <编程简介(Java) ·10.3递归思想> 10.3.1 递归的概念 以两种方式的人:男人和女人:算法是两种:递归迭代/通知: 递归方法用自己的较简单的情形定义自己. 在数学和计算机科学 ...

  9. MIT 计算机科学及编程导论 Python 笔记 1

    计算机科学及编程导论在 MIT 的课程编号是 6.00.1,是计算机科学及工程学院的经典课程.之前,课程一直使用 Scheme 作为教学语言,不过由于 Python 简单.易学等原因,近年来已经改用 ...

随机推荐

  1. PIE SDK 坐标系创建、定义、对比

    1.    坐标系创建 1.1    从WKT字符串导入空间参考 ISpatialReference接口是一个任何空间参考对象都实现的接口,它包含了所有空间参考对象都公有的方法和属性,如获得空间参考对 ...

  2. 「BZOJ1924」「SDOI2010」 所驼门王的宝藏 tarjan + dp(DAG 最长路)

    「BZOJ1924」[SDOI2010] 所驼门王的宝藏 tarjan + dp(DAG 最长路) -------------------------------------------------- ...

  3. python 爬虫系列01-连接mysql

    爬虫学习中......................................... import pymysql conn = pymysql.connect(host=',database ...

  4. eclipse中注释快捷键

    手动注释: ①类注释:Shift+Alt+J ②方法注释:在方法上方输入/** 后点击回车 自动注释:点击菜单栏上的Window -->Preferences-->Java-->Co ...

  5. (转)创建DB2实例时出错,请大家帮忙解决

    创建DB2实例时出错,请大家帮忙解决 原文:http://bbs.chinaunix.net/thread-3601748-1-1.html 运行:$DB2DIR/instance/db2icrt   ...

  6. C++11并发编程:async,future,packaged_task,promise

    一:async std::async:用于创建异步任务,可以代替创建线程,函数原型:async(std::launch::async | std::launch::deferred, f, args. ...

  7. 使用awstat分析Nginx的访问日志

    在我的上一篇文章<使用 Nginx 提升网站访问速度>中介绍了 Nginx 这个 HTTP 服务器以及如何通过它来加速网站的访问速度.在实际的网站运营中,我们经常需要了解到网站的访问情况, ...

  8. Windows x64位通过PEB获得Kernel32基地址

    在64位系统下 gs:[0x30] 指向TEB gs:[0x60] 指向PEB kd> dt _TEB nt!_TEB +0x000 NtTib : _NT_TIB +0x000 Excepti ...

  9. 对key中有数字的字典进行排序

    word_cloud = []cc = [{"c58":341,"c59":525,"c56":507,"c57":34 ...

  10. bzoj 5291: [Bjoi2018]链上二次求和

    Description 有一条长度为n的链(1≤i<n,点i与点i+1之间有一条边的无向图),每个点有一个整数权值,第i个点的权值是 a_i.现在有m个操作,每个操作如下: 操作1(修改):给定 ...