版权声明:本文为博主原创文章,未经博主同意不得转载。 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."。

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

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

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

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


转载于:https://www.cnblogs.com/mqxnongmin/p/10612550.html

《编程导论(Java)·9.3.1回调·3》回调的实现相关推荐

  1. java中能构成循环的语句_《编程导论(Java)#183;3.2.4 循环语句》

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

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

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

  3. 《编程导论(Java)·附录A 使用BlueJ》

    1. BlueJ的特点 使用Java语言进行软件开发,有很多集成开发环境(IntegretedDevelopment Enviroment,IDE)可选择.本教材使用的是BlueJ.BlueJ专门针对 ...

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

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

  5. 计算机编程导论python程序设计答案-学堂在线_计算机科学与Python编程导论_作业课后答案...

    学堂在线_计算机科学与Python编程导论_作业课后答案 答案: 更多相关问题 近代中国完全沦为半殖民地半封建社会的标志是:A.<马关条约>B.<辛丑条约>C.<凡尔赛和 ...

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

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

  7. JUC并发编程(java util concurrent)(哔站 狂神说java juc并发编程 摘录笔记)

    JUC并发编程(java util concurrent) 1.什么是JUC JUC并不是一个很神秘的东西(就是 java.util 工具包.包.分类) 业务:普通的线程代码 Thread Runna ...

  8. java响应式编程有几种方式_什么是响应式编程,Java 如何实现

    什么是响应式编程,Java 如何实现 我们这里用通过唯一 id 获取知乎的某个回答作为例子,首先我们先明确下,一次HTTP请求到服务器上处理完之后,将响应写回这次请求的连接,就是完成这次请求了,如下: ...

  9. PYTHON编程导论群问题汇总(一)

    问题1 [Jane] 课程视频用的是python 2.7并推荐了一个软件 可是书上用的是python 3 所以是安装视频推荐的软件来学习 还是用自己的python 3好呢 Bigjing 推荐pyth ...

最新文章

  1. 操作系统2--操作系统结构
  2. 作为候选人,你需要问些什么?你需要查些什么?这些关乎你的利益和未来!
  3. 【神经网络】MLP 编码器-解码器 注意力机制 残差连接
  4. 聚类Introducion
  5. mysql的递归查询_比较两种mysql递归tree查询效率-mysql递归tree
  6. MATLAB神经网络应用之Elman神经网络
  7. lisp 多段线转面域_CAD自定义线型,lisp多段线绘制方向 | 伙计百科
  8. 项目集与项目群、项目组合的区别
  9. 使用Flask部署机器学习模型
  10. Excel表格不能插入行解决方案
  11. 学习PMbok对pmp考试的认知理解和itto输入输出的整理笔记
  12. 鄂尔多斯固体废物智慧化管理平台设备和功能概况
  13. 【Android】aapt2报错:windows W/ziparchive( 2348): Unable to open ‘badging‘: No such file or directory
  14. IMDB Top 250电影推荐
  15. Java 动手写爬虫: 三、爬取队列
  16. 所有人体胸部和下半身各部位的英语单词
  17. 从订单信息页面进入订单详细商品页面,最简单的MVC思想
  18. [转载]删除微软拼音输入法
  19. Ruby+Watir搭建自动化测试框架
  20. 融云猿桌派开放报名:扶我起来,我能干到36岁

热门文章

  1. mysql openrowset_SQL的OPENROWSET开启和使用方法
  2. Visual Studio Code是什么
  3. 如何理解有人说“真正的奇石收藏,从做减法开始”?
  4. 在职场中,如何能够让下属认真完成工作,又不顶撞你?
  5. 活的通透的人,会是一个什么样的人?
  6. 真正好的东西,就会脱颖而出
  7. 如果你现在很穷,很苦,不要苦恼
  8. 为啥arm架构比x86 x64省电?
  9. 高并发下如何生成唯一订单号?
  10. sql limit 子句_SQL Server中的FOR XML PATH子句