Java专家之路-- 面向对象的基础概念和原理,使用回调机制实现对象之间通信
Posted 独孤文彬
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java专家之路-- 面向对象的基础概念和原理,使用回调机制实现对象之间通信相关的知识,希望对你有一定的参考价值。
文章目录
基础概念理解:
什么叫对象间的通信?
通俗来讲,就是a 对象 调用 b 对象的方法(接口)
界定范围:哪些对象的通信?
本地:在同一个jvm内,称为本地对象的方法、接口调用
远程:在不同的jvm中,称为远程的方法、接口调用
对象间通信的方式:
同步:单向,阻塞式的调用
回调:双向(分阻塞、非阻塞两种)
异步:双向,且非阻塞。所以,由此可见,回调和异步通常是成对出现。回调是异步的基础,异步是回调的一种表现形式。
对于回调进行定义:
在计算机程序设计中,回调函数,或简称回调(Callback 即call then back 被主函数调用运算后会返回主函数),是指通过函数参数传递到其它代码的,某一块可执行代码的引用。这一设计允许了底层代码调用在高层定义的子程序。
常见的一些对于回调的偏见或者错误认知
以偏概全:
例一:GUI编程中,一个按钮被点击之后要做点事情,大家可以注册个ActionListener上去监听点击事件,在点击时被调用,这就是一个回调。
例二:例如说java.util.Collections.sort(List, Comparator),这个sort()方法就定义了一个流程实现排序,而具体的顺序则由传入的Comparator参数来确定——这就是回调。
其实,上面两个例子,都没有错。但是,都发生了以偏概全的错误定义。
望文生义:
混淆概念:
当遇到一些IO或者延迟等需要耗用时间的操作,我们一般采用回调的方式来完成。就是你委托JVM或者操作系统做一些事情,他们做好了,就会回调你的方法让你去处理。
小结:那么如何全面认知一个概念,或者说如何科学地给一个概念下定义呢?一般来说,我们定义一个概念,从内涵和外延两个方面进行说明。
内涵(本质)
就是一个可执行代码块的引用,或者说内存地址,或者说是指针,其实都是一回事儿。
(备注:可执行代码块,它有很多别名,例如: 函数/方法/过程/事件对象/委托,这些概念其实都是一回事儿)
外延(相关的概念,延伸的意义)
1、回调和相关设计模式(观察者、访问者、……)的关系:
模式,是通过接口回调的方法实现的,即它是一种回调的体现。
而回调,本身是一种原理,机制。而非具体的实现。
2、与异步同步的关系:回调分为异步的回调、同步的回调。经常和异步成对出现。
3、和事件驱动(event、listener)的关系:回调,是事件驱动编程的实现的基础原理。委托是c#中的概念,其实和匿名函数、java中的函数式接口、接口回调是同一个概念的不同表达形式。他们都是用来实现事件驱动编程(控制框架)的核心。
4、和javaEE的web编程servlet中filter技术的关系:filter技术是回调机制的具体应用场景之一,filter核心技术:基于回调、责任链模式
5、回调和闭包的关系。
回调函数:
这一般是在C语言中这么称呼,对于定义一个函数,但是并不由自己去调用,而是由被调用者间接调用,都可以叫回调函数。本质上,回调函数和一般的函数没有什么区别,也许只是因为我们定义了一个函数,却从来没有直接调用它,这一点很奇怪,所以有人发明了回调函数这个词来统称这种间接的调用关系。
在包括C#在内的很多高级语言中,我们有其它更具体的名词来称呼它,比如事件处理函数,委托,匿名委托,匿名函数,Lambda表达式,所以很少直接称呼某个函数为回调函数,除非是编写和C打交道的程序。
闭包:
在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久。
小结:
回调虽然和闭包是在实现上和功能上类似,但是,闭包我们知道了,更倾向于拓宽变量的作用域或类似接口封装
我们可以在回调函数中做任何我们可以做的事情,这当然也就包括访问我们自己的变量(从而达到和闭包一样的效果)
分类
- 同步和异步(阻塞、非阻塞):
阻塞式回调里,回调函数的调用一定发生在起始函数返回之前;
而延迟式回调里,回调函数的调用有可能是在起始函数返回之后
- 推模式、拉模式(被动推、主动拉):
推模式的是指,被调用方主动将其地址推送给调用者。
示例代码:
//接口:
public interface GasListener
public void offerGas(String msg);
//接口实现的甲方:
public class GasCompany implements GasListener
public void advertiseTo(IndoorsMan man)
System.out.println("煤气公司:这是我们的联系方式,欢迎来电!");
man.setListener(this);
@Override
public void offerGas(String msg)
System.out.println("煤气公司接收的订单:"+msg);
//调用者:
public class IndoorsMan
GasListener gListener;
public void prepareCook()
System.out.println("宅男:准备下厨做几个花式大菜!");
System.out.println("宅男:进厨房,烧菜...");
System.out.println("宅男:刚开火,就发现煤气不足,没办法,只能打电话叫煤气。");
gListener.offerGas("宅男:送一瓶煤气过来!");
public void setListener(GasListener gListener)
this.gListener = gListener;
测试:
public class Test
public static void main(String[] args)
IndoorsMan man = new IndoorsMan();
GasCompany company = new GasCompany();
company.advertiseTo(man);
man.prepareCook();
主动拉模式:调用者自己主动获取甲方的信息。
示例代码:
public class IndoorsMan
GasListener gListener;
public void prepareCook()
System.out.println("宅男:准备下厨做几个花式大菜!");
System.out.println("宅男:进厨房,烧菜...");
System.out.println("宅男:刚开火,就发现煤气不足,没办法,只能打电话叫煤气。");
gListener.offerGas("宅男:送一瓶煤气过来!");
public void setListener(GasListener gListener)
this.gListener = gListener;
public void configureGas()
// 主动获取甲方信息
setListener(new GasListener()
@Override
public void offerGas(String msg)
System.out.println("下单内容:"+msg);
);
//测试
public class Test
public static void main(String[] args)
IndoorsMan man = new IndoorsMan();
man.configureGas();
man.prepareCook();
扩展认知
异曲同工之妙的技术:代理、反射。(aop、interceptor)
利用代理和反射,也能实现另类的回调。因此我们可以说,在代理和反射中,也应用到了回调机制。
回调原理和机制:
为什么需要它?(应用场景)
-
1、灵活、扩展、解耦:
为了使我们写的函数接近完美,就把一部分功能外包给别人,让别人个性化定制,至于别人怎么实现不管,我唯一要做的就是定义好相关接口,把调用者与被调用者分开,所以调用者不关心谁是被调用者(解耦)。这一设计允许了底层代码调用高层定义的子程序(IoC控制反转),增强程序灵活性,和反射有着异曲同工之妙。也满足和实现**好莱坞原则(don’t call me,i will call you !)
-
2、回调可用于通知机制
事件驱动的GUI程序,交互式的界面开发、组件开发等。
例如:GUI编程中,一个按钮被点击之后要做点事情,大家可以注册个ActionListener上去监听点击事件,在点击时被调用,这就是一个异步回调。 -
3、Windows平台的消息机制
-
4、异步的接口调用(Ajax技术、远程调用等)
-
5、Servlet中的Filter(过滤器)是基于回调函数,需容器支持。
-
6、深入学习和理解框架的必备基础。
各种框架中广泛应用回调接口来支持扩展、解耦、自动化控制处理等操作,例如:Springmvc、Spring、structs、JSF等
如何实现回调?
其他语言或者平台
C、C++和Pascal:
允许将函数指针作为参数传递给其它函数。其它语言,例如javascript,Python,Perl[1][2]和php,允许简单的将函数名作为参数传递。
Objective-C
中允许利用@selector关键字传递SEL类型的函数名。在实现中,SEL类型被定义为函数名字符串。
在类似于C#与VB.NET的运用.NET Framework的语言中
,提供了一种型别安全的引用封装,所谓的’委托’,用来定义包含类型的函数指针,可以用于实现回调。
.NET语言
中用到的事件与事件处理函数提供了用于回调的通用语法。
函数式编程语言
通常支持第一级函数,可以作为回调传递给其它函数,也可以作为数据类型存储或是返回给其它函数。
某些语言,比如Algol 68,Perl,新版本的.NET语言以及多数函数式编程语言中
允许使用匿名的代码块(lambda表达式),用以代替在别处定义的独立的回调函数。
在Apple或是LLVM的C语言扩展中,包含称为块的语言特性,可以作为函数的参数传递,作为回调的一种实现。
在缺少函数类型的参数的面向对象的程序语言中(备注:java8 以后支持了函数式编程,引入了lambda表达式、函数式接口),例如Java,回调可以用传递抽象类或接口来模拟。回调的接收者会调用抽象类或接口的方法,这些方法由调用者提供实现。这样的对象通常是一些回调函数的集合,同时可能包含它所需要的数据。这种方法在实现某些设计模式时比较有用,例如访问者模式,观察者模式与策略模式。
C++
允许对象提供其自己的函数调用操作的实现,即重载operator()。标准模板库和函数指针一样接受这类对象(称为函数对象)作为各种算法的参数。
Java 中的回调
Java是一门面向对象语言,一切皆对象,因此在Java中不存在回调函数这一说法的。由于Java的一切皆对象性质,从而将回调函数这个特性提升到了接口回调。
接口回调:可以把使用某一接口的类创建的对象的引用赋给该接口声明的接口变量,那么该接口变量就可以调用被类实现的接口的方法。实际上,当接口变量调用被类实现的接口中的方法时,就是通知相应的对象调用接口的方法,这一过程称为对象功能的接口回调。
从概念可以看出,接口回调是指一个使用过程,并强调是关于对象功能的使用过程,既然是功能,功能一般就对应着方法体(函数),因此它同样满足与回调函数相似的模型。
接口回调的机制与回调函数的机制类似:
(1)定义一个接口;
(2)提供接口实现的一方在初始化的时候,将接口回调的引用注册给调用者;
(3)当特定的事件或条件发生的时候,调用者使用引用调用实现的接口方法对事件进行处理。
java 中回调的实现方式源码参考:
https://github.com/pzemtsov/article-java-callback/blob/master/FLambda.java
附:
参考文章
https://www.cnblogs.com/duanxz/p/3508272.html
https://zh.wikipedia.org/wiki/回调函数
https://github.com/Gentle-Lee/Gentle-Lee.github.io/issues/3
https://www.cnblogs.com/brucheium/p/3825060.html
https://www.bysocket.com/?p=636
以上是关于Java专家之路-- 面向对象的基础概念和原理,使用回调机制实现对象之间通信的主要内容,如果未能解决你的问题,请参考以下文章