如何用普通英语解释回调?它们与从调用函数获取某个上下文的另一函数调用另一个函数有何不同?如何向新手程序员解释其功能?
如何用普通英语解释回调?它们与从调用函数获取某个上下文的另一函数调用另一个函数有何不同?如何向新手程序员解释其功能?
Answers:
通常,应用程序需要根据其上下文/状态执行不同的功能。为此,我们使用一个变量来存储有关要调用的函数的信息。application根据需要,应用程序将使用有关要调用的函数的信息来设置此变量,并将使用相同的变量来调用该函数。
在javascript中,示例如下。在这里,我们使用方法参数作为变量,在其中存储有关函数的信息。
function processArray(arr, callback) {
var resultArr = new Array();
for (var i = arr.length-1; i >= 0; i--)
resultArr[i] = callback(arr[i]);
return resultArr;
}
var arr = [1, 2, 3, 4];
var arrReturned = processArray(arr, function(arg) {return arg * -1;});
// arrReturned would be [-1, -2, -3, -4]
function(arg)
的)processArray(arr,callback)
功能
我将尝试使这一死角变得简单。“回调”是由将第一个功能作为参数的另一个功能调用的任何功能。很多时候,“回调”是在发生某些事情时调用的函数。这东西可以称得上是程序员发言“事件”。
想象一下这种情况:您将在几天后收到一个包裹。包装是给邻居的礼物。因此,一旦获得包裹,就希望将其带给邻居。您不在城里,所以您留下了配偶的指示。
您可以告诉他们拿到包裹并将其带到邻居。如果您的配偶像计算机一样愚蠢,他们会坐在门口等待包裹,直到它来了(别无所求),然后一旦到达,他们就会把它带给邻居。但是有更好的方法。告诉您的配偶,他们一旦收到包裹,便应将包裹带到邻居身边。然后,直到收到包裹,他们才能正常生活。
在我们的示例中,包的接收是“事件”,而将其带到邻居是“回调”。您的配偶“执行”您的指示以仅在包裹到达时才带走包裹。好多了!
这种想法在日常生活中很明显,但是计算机没有相同的常识。考虑程序员通常如何写入文件:
fileObject = open(file)
# now that we have WAITED for the file to open, we can write to it
fileObject.write("We are writing to the file.")
# now we can continue doing the other, totally unrelated things our program does
在这里,在写入文件之前,我们等待文件打开。这“阻塞”了执行流程,我们的程序无法执行它可能需要执行的其他任何操作!如果我们可以这样做,该怎么办:
# we pass writeToFile (A CALLBACK FUNCTION!) to the open function
fileObject = open(file, writeToFile)
# execution continues flowing -- we don't wait for the file to be opened
# ONCE the file is opened we write to it, but while we wait WE CAN DO OTHER THINGS!
事实证明,我们使用某些语言和框架来做到这一点。它太酷了!查看Node.js,以这种思维获得一些实际的实践。
open
工作原理做出了假设。可能open
会在等待操作系统执行其不可思议的操作时在内部阻塞,然后执行回调。在这种情况下,结果没有差异。
如何用普通英语解释回调?
用简单的英语来说,回调函数就像一个Worker,当他完成Task时会“回调”给他的Manager。
它们与从调用函数获取某个上下文的另一函数调用另一个函数有何不同?
确实是从另一个函数调用一个函数,但是关键是回调被当作对象对待,因此您可以根据系统状态(例如“策略设计模式”)来更改要调用的函数。
如何向新手程序员解释其功能?
回调的功能可以在需要从服务器提取数据的AJAX风格的网站中轻松看到。下载新数据可能需要一些时间。如果没有回调,则整个用户界面将在下载新数据时“冻结”,或者您需要刷新整个页面,而不仅仅是部分页面。通过回调,您可以插入“正在加载”图像,并在加载新数据后将其替换为新数据。
function grabAndFreeze() {
showNowLoading(true);
var jsondata = getData('http://yourserver.com/data/messages.json');
/* User Interface 'freezes' while getting data */
processData(jsondata);
showNowLoading(false);
do_other_stuff(); // not called until data fully downloaded
}
function processData(jsondata) { // do something with the data
var count = jsondata.results ? jsondata.results.length : 0;
$('#counter_messages').text(['Fetched', count, 'new items'].join(' '));
$('#results_messages').html(jsondata.results || '(no new messages)');
}
这是一个使用jQuery的getJSON的回调示例:
function processDataCB(jsondata) { // callback: update UI with results
showNowLoading(false);
var count = jsondata.results ? jsondata.results.length : 0;
$('#counter_messages').text(['Fetched', count, 'new items'].join(' '));
$('#results_messages').html(jsondata.results || '(no new messages)');
}
function grabAndGo() { // and don't freeze
showNowLoading(true);
$('#results_messages').html(now_loading_image);
$.getJSON("http://yourserver.com/data/messages.json", processDataCB);
/* Call processDataCB when data is downloaded, no frozen User Interface! */
do_other_stuff(); // called immediately
}
通常,回调需要state
使用a从调用函数进行访问closure
,这就像Worker在完成Task之前需要从Manager中获取信息一样。要创建,您可以内联函数,以便它可以在调用上下文中查看数据:closure
/* Grab messages, chat users, etc by changing dtable. Run callback cb when done.*/
function grab(dtable, cb) {
if (null == dtable) { dtable = "messages"; }
var uiElem = "_" + dtable;
showNowLoading(true, dtable);
$('#results' + uiElem).html(now_loading_image);
$.getJSON("http://yourserver.com/user/"+dtable+".json", cb || function (jsondata) {
// Using a closure: can "see" dtable argument and uiElem variables above.
var count = jsondata.results ? jsondata.results.length : 0,
counterMsg = ['Fetched', count, 'new', dtable].join(' '),
// no new chatters/messages/etc
defaultResultsMsg = ['(no new ', dtable, ')'].join('');
showNowLoading(false, dtable);
$('#counter' + uiElem).text(counterMsg);
$('#results'+ uiElem).html(jsondata.results || defaultResultsMsg);
});
/* User Interface calls cb when data is downloaded */
do_other_stuff(); // called immediately
}
// update results_chatters when chatters.json data is downloaded:
grab("chatters");
// update results_messages when messages.json data is downloaded
grab("messages");
// call myCallback(jsondata) when "history.json" data is loaded:
grab("history", myCallback);
最后,这里是一个定义closure
从道格拉斯·克罗克福德:
可以在其他函数内部定义函数。内部函数可以访问外部函数的var和参数。如果对内部函数的引用仍然存在(例如,作为回调函数),则外部函数的var也将保留。
也可以看看:
令我惊讶的是,有这么多聪明的人没有强调这样的现实,即“回叫”一词已经以两种不一致的方式被使用。
两种方式都涉及通过将附加功能(功能定义,匿名或命名)传递给现有功能来定制功能。即。
customizableFunc(customFunctionality)
如果将自定义功能简单地插入到代码块中,则说明您已经自定义了该功能。
customizableFucn(customFunctionality) {
var data = doSomthing();
customFunctionality(data);
...
}
尽管这种注入的功能通常称为“回调”,但并没有任何条件。一个非常明显的示例是forEach方法,其中提供了一个自定义函数作为自变量,以将其应用于数组中的每个元素以修改数组。
但这从根本上不同于在异步编程中使用“回调”函数(例如在AJAX或node.js中),或者仅是在向用户交互事件(例如鼠标单击)分配功能时。在这种情况下,整个想法是在执行自定义功能之前等待偶然事件发生。这在用户交互的情况下是显而易见的,但在可能要花费时间的I / O(输入/输出)过程中也很重要,例如从磁盘读取文件。这是术语“回调”最明显的地方。一旦启动I / O进程(例如,请求从磁盘读取文件或从HTTP请求返回数据的服务器),异步程序不会等待它完成。它可以继续执行接下来计划的任何任务,并且只有在已通知读取文件或http请求已完成(或失败)并且数据可用于自定义功能之后,才使用自定义功能进行响应。这就像在电话上打电话给企业,并留下您的“回叫”号码,因此当有人可以与您联系时,他们可以给您打电话。这比知道谁能待多久又无法参加其他事务的电话挂线要好。
异步使用本质上涉及侦听所需事件(例如,I / O过程的完成)的某种方式,以便在发生(且仅在发生时)执行自定义的“回调”功能。在一个明显的AJAX示例中,当数据实际从服务器到达时,将触发“回调”功能以使用该数据来修改DOM,从而在此程度上重绘浏览器窗口。
回顾一下。有些人使用“回调”一词来指代任何可以作为参数注入到现有函数中的自定义函数。但是,至少对我而言,最合适的用法是异步使用注入的“回调”功能-仅在发生等待通知的事件时执行。
Array.prototype.forEach()
,并通过作为对Arg的功能setTimeout()
,它们是不同颜色的马远,你有理由对你的程序的方式。
用非程序员的术语来说,回调是程序中的空白。
在许多纸质表格上,一个常见的项目是“紧急情况下要打电话的人”。那里有一个空白行。您输入某人的姓名和电话号码。如果发生紧急情况,该人员将被呼叫。
这是关键。您无需更改表单(代码,通常是其他人的表单)。但是,您可以填写缺少的信息(您的电话号码)。
范例1:
回调用作自定义方法,可能用于添加/更改程序的行为。例如,采用一些执行功能但不知道如何打印输出的C代码。它所能做的就是创建一个字符串。当它试图弄清楚该字符串如何处理时,它会看到一个空行。但是,程序员给了您空白来编写您的回调!
在此示例中,您不使用铅笔在一张纸上填充空白,而是使用功能set_print_callback(the_callback)
。
set_print_callback
是铅笔,the_callback
是您要填写的信息。现在,您已在程序中填写此空白行。每当需要打印输出时,它都会查看该空白行,并按照其中的说明进行操作(即调用您放置在此处的函数。)实际上,这允许将其打印到屏幕,日志文件,打印机,通过网络连接或其任何组合。您已将要执行的操作填入空白。
范例2:
当您被告知需要拨打紧急电话时,您可以去阅读纸质表格上写的内容,然后再拨打已阅读的电话号码。如果该行为空,则不会执行任何操作。
Gui编程的工作方式大致相同。单击按钮后,程序需要弄清楚下一步该怎么做。它去寻找回调。此回调恰好在标有“单击Button1时的操作”的空白中
当您要求(例如button1_clicked
)时,大多数IDE会自动为您填充空白(编写基本方法)。但是,该空白可以有您擅长的任何方法。您可以调用该方法run_computations
或butter_the_biscuits
只要将该回调的名称放在适当的空白中即可。您可以在紧急电话号码空白处输入“ 555-555-1212”。这没有多大意义,但是是允许的。
最后说明:您要在回调中填写的空白行吗?可以随意擦除和重写它。(是否应该是另一个问题,但这是他们权力的一部分)
总是最好从一个例子开始:)。
假设您有两个模块A和B。
您希望在模块B中发生某些事件/情况时通知模块A。但是,模块B不了解您的模块A。它所知道的只是通过功能指针指向(模块A的)特定功能的地址。由模块A提供给它。
因此,当使用功能指针发生特定的事件/条件时,B要做的就是将其“回调”到模块A中。可以在回调函数中做进一步的处理。
*)这里的一个明显优势是,您正在从模块B中提取有关模块A的所有内容。模块B不必关心模块A是谁/什么。
约翰尼(Johny)程序员需要一个订书机,于是他下到办公室供应部门并要求一个订书机。填写请求表后,他可以站在那儿,等待店员到仓库里寻找订书机(例如阻塞功能调用) ),或者同时进行其他操作。
由于这通常需要时间,因此约翰尼将便条纸和请求表放在一起,要求他们在订书机准备好取书时打电话给他,因此他可以去做其他事情,例如在书桌上打n。
想象一下,您需要一个返回10平方的函数,因此您编写了一个函数:
function tenSquared() {return 10*10;}
稍后,您需要9平方,以便编写另一个函数:
function nineSquared() {return 9*9;}
最终,您将所有这些替换为通用函数:
function square(x) {return x*x;}
完全相同的想法适用于回调。您有一个执行某些操作的函数,完成后会调用doA:
function computeA(){
...
doA(result);
}
稍后,您希望使用完全相同的函数来调用doB,而您可以复制整个函数:
function computeB(){
...
doB(result);
}
或者,您可以将回调函数作为变量传递,而只需具有一次该函数:
function compute(callback){
...
callback(result);
}
然后,您只需要调用compute(doA)和compute(doB)。
除了简化代码之外,它还允许异步代码通过在完成时调用任意函数来让您知道它已完成,类似于在电话中呼叫某人并留下回叫号码时。
有两点需要说明,一是回调的工作原理(绕过一个可以在不了解其上下文的情况下调用的函数),二是它的用途(异步处理事件)。
一个被其他答案使用的等待包裹到达的类比可以很好地解释这两种情况。在计算机程序中,您将告诉计算机要包裹。通常,它现在会坐在那里,等待(什么也不做)直到包裹到达,如果它永远不会到达,可能会无限期地到达。对人类来说,这听起来很愚蠢,但是如果不采取进一步措施,这对于计算机来说是完全自然的。
现在,回调将成为您前门的铃铛。您为包裹服务提供了一种通知包裹到达的方式,而无需他们知道您在房子里的什么地方,或者门铃如何工作。(例如,某些“铃声”实际上调度了一个电话。)由于您提供了可以随时调用的“回调功能”,因此,您可以在上下文无关的情况下停止坐在前廊并“处理”事件”(包裹到达)。
想象一个朋友要离开你的房子,你告诉她“回家后打电话给我,以便我知道你已经安全抵达了”。(从字面上看)是回叫。这就是回调函数,与语言无关。您希望某个过程在完成某些任务后将控制权交还给您,因此您给它提供了一个函数,用于回调给您。
例如,在Python中,
grabDBValue( (lambda x: passValueToGUIWindow(x) ))
grabDBValue
可以编写为仅从数据库中获取值,然后让您指定对该值实际执行的操作,因此它接受一个函数。您不知道何时或是否grabDBValue
会返回,但是如果/何时返回,您知道您想要它做什么。在这里,我传入了一个匿名函数(或lambda),该函数将值发送到GUI窗口。通过执行以下操作,我可以轻松更改程序的行为:
grabDBValue( (lambda x: passToLogger(x) ))
回调在以函数为一等值的语言中效果很好,就像通常的整数,字符串,布尔值等。在Java中,调用者会要求使用某种方法名称的某种类型的静态类,因为类之外没有函数(实际上是“方法”);在大多数其他动态语言中,您只需传递具有简单语法的函数即可。
在具有词法作用域的语言(例如Scheme或Perl)中,您可以使用以下技巧:
my $var = 2;
my $val = someCallerBackFunction(sub callback { return $var * 3; });
# Perlistas note: I know the sub doesn't need a name, this is for illustration
$val
在这种情况下,是6
因为回调可以访问在定义它的词法环境中声明的变量。词法作用域和匿名回调是一个强大的组合,需要新手程序员进一步研究。
您有一些要运行的代码。通常,当您调用它时,您会在继续操作之前等待它完成(这可能会导致您的应用变灰/产生光标旋转时间)。
另一种方法是并行运行此代码,然后继续自己的工作。但是,如果原始代码需要根据调用代码的响应来做不同的事情怎么办?好吧,在这种情况下,您可以传递完成后要调用的代码的名称/位置。这是“回叫”。
正常代码:索取信息->流程信息->处理结果->继续执行其他操作。
使用回调:询问信息->流程信息->继续执行其他操作。并在以后的某个地方处理处理结果。
在没有回调的情况下,没有其他特殊的编程资源(例如线程和其他资源),一个程序就是一系列指令,这些指令一个接一个地依次执行,甚至具有某种由某些条件决定的“动态行为”,所有可能的情况应事先编程。
因此,如果我们需要为程序提供真实的动态行为,则可以使用回调。使用回调,您可以按参数指示,一个程序可以调用另一个程序,该程序提供一些先前定义的参数,并且可以预期一些结果(这是合同或操作签名),因此这些结果可以由第三方程序产生/处理。以前不知道。
该技术是应用于计算机程序运行的程序,函数,对象和所有其他统一代码的多态性的基础。
当您完成某项工作时,以人类世界为例进行了很好的解释,假设您是画家(这里是您的主程序,即画家),有时会打电话给客户,要求他批准您的工作结果,因此,他决定图片是否良好(您的客户是第三方程序)。
在上面的示例中,您是画家,并向他人“委托”工作以批准结果,图片是参数,每个新客户端(称为“功能”)都会更改工作结果,以决定他想要什么关于图片(客户的决定是“回调函数”的返回结果)。
我希望这个解释会有用。
假设您要给我一个可能会长期运行的任务:获取遇到的前五个独特人员的姓名。如果我在人烟稀少的地区,则可能需要几天的时间。当我四处奔跑时,您对坐在椅子上并不是很感兴趣,所以您说:“找到清单后,在手机上给我打电话,然后读给我听。这是电话号码。”
您已经给了我一个回调引用-我应该执行该函数以便进行进一步的处理。
在JavaScript中,可能看起来像这样:
var lottoNumbers = [];
var callback = function(theNames) {
for (var i=0; i<theNames.length; i++) {
lottoNumbers.push(theNames[i].length);
}
};
db.executeQuery("SELECT name " +
"FROM tblEveryOneInTheWholeWorld " +
"ORDER BY proximity DESC " +
"LIMIT 5", callback);
while (lottoNumbers.length < 5) {
playGolf();
}
playLotto(lottoNumbers);
这可能可以通过许多方式加以改进。例如,您可以提供第二个回调:如果最终花费了一个多小时,请拨打红色电话,并告诉对方您超时的答案。
回调是将由第二个函数调用的函数。第二个函数事先不知道它将调用什么函数。因此,回调函数的标识存储在某个地方,或作为参数传递给第二个函数。根据编程语言的不同,“身份”可能是回调的地址或某种其他类型的指针,也可能是函数的名称。主体是相同的,我们存储或传递一些明确标识功能的信息。
时间到了,第二个函数可以调用回调,并根据当时的情况提供参数。它甚至可能从一组可能的回调中选择该回调。编程语言必须提供某种语法,以允许第二个函数在知道其“身份”的情况下调用回调。
这种机制有很多可能的用途。使用回调,函数的设计者可以通过调用提供的任何回调函数来对其进行自定义。例如,排序函数可能将回调作为参数,并且此回调可能是用于比较两个元素以确定哪个最先出现的函数。
顺便说一句,根据编程语言的不同,上面讨论中的“功能”一词可以用“块”,“闭包”,“ lambda”等代替。
通常我们将变量发送给函数。假设您有任务需要在将变量作为参数指定之前进行处理-您可以使用callback。
function1(var1, var2)
是通常的方式。
如果要var2
处理然后作为参数发送该怎么办?
function1(var1, function2(var2))
这是回调的一种类型- function2
执行一些代码并将变量返回到初始函数。
对于示教回调,您必须首先示教指针。一旦学生理解了指向变量的指针的想法,回调的想法就会变得更加容易。假设您正在使用C / C ++,则可以遵循以下步骤。
可能还有更多的事情。让学生参与进来,他们会发现。希望这可以帮助。
用简单的英语来说,回调是一个承诺。乔,简,大卫和萨曼莎共用一辆车上班。乔今天开车。简,大卫和萨曼莎有两种选择:
选项1:这更像是一个轮询示例,其中Jane会陷入“循环”中,检查Joe是否在外面。简在此期间无能为力。
选项2:这是回调示例。简告诉乔在外面时给她敲门铃。她给他“门铃”的“功能”。Joe不需要知道门铃的工作原理或位置,他只需要调用该功能即可,即在门铃响时就给门铃响。
回调由“事件”驱动。在此示例中,“事件”是乔的到来。例如,在Ajax中,事件可以是异步请求的“成功”或“失败”,并且每个事件可以具有相同或不同的回调。
在JavaScript应用程序和回调方面。我们还需要了解“闭包”和应用程序上下文。“ this”所指的内容很容易使JavaScript开发人员感到困惑。在此示例中,在每个人的“ ring_the_door_bell()”方法/回调中,每个人可能需要根据自己的早晨例行事例执行其他一些方法。“ turn_off_the_tv()”。我们希望“ this”引用“ Jane”对象或“ David”对象,以便每个人都可以设置在Joe拿起它们之前需要做的其他事情。在此处使用Joe设置回调需要模仿该方法,以便“ this”引用正确的对象。
希望有帮助!
什么是回调函数?
对第一个问题的简单回答是,回调函数是通过函数指针调用的函数。如果将一个函数的指针(地址)作为参数传递给另一个函数,则当该指针用于调用该函数时,它指向该函数,即表示已进行回调。
回调函数很难跟踪,但有时它非常有用。特别是在设计库时。回调函数就像要求您的用户给您一个函数名称,您将在特定条件下调用该函数。
例如,您编写一个回调计时器。它允许您指定持续时间和要调用的函数,并且该函数将相应地回调。“每10秒运行myfunction()5次”
或者,您可以创建一个函数目录,传递一个函数名列表,并要求库进行相应的回调。“如果成功,则回调成功(),如果失败,则回调失败()。”
让我们看一个简单的函数指针示例
void cbfunc()
{
printf("called");
}
int main ()
{
/* function pointer */
void (*callback)(void);
/* point to your callback function */
callback=(void *)cbfunc;
/* perform callback */
callback();
return 0;
}
如何将参数传递给回调函数?
观察到实现回调的函数指针采用了void *,这表明它可以采用任何类型的变量,包括结构。因此,您可以按结构传递多个参数。
typedef struct myst
{
int a;
char b[10];
}myst;
void cbfunc(myst *mt)
{
fprintf(stdout,"called %d %s.",mt->a,mt->b);
}
int main()
{
/* func pointer */
void (*callback)(void *); //param
myst m;
m.a=10;
strcpy(m.b,"123");
callback = (void*)cbfunc; /* point to callback function */
callback(&m); /* perform callback and pass in the param */
return 0;
}
回调是计划在满足条件时执行的方法。
一个“真实世界”的例子是本地视频游戏商店。您正在等待《半条命3》,而无需每天去商店看看是否存在,而是在列表中注册了电子邮件,以便在游戏可用时得到通知。电子邮件将成为您的“回叫”,并且满足条件是游戏的可用性。
“程序员”示例是一个网页,您想在其中单击按钮时执行操作。您注册按钮的回调方法,然后继续执行其他任务。当/如果用户单击按钮,浏览器将查看该事件的回调列表并调用您的方法。
回调是一种异步处理事件的方法。您永远无法知道何时执行回调,或者根本不执行回调。优点是,它可以在等待答复的同时释放程序和CPU周期来执行其他任务。
“在计算机编程中,回调是对可执行代码或一段可执行代码的引用,该代码作为参数传递给其他代码。这允许较低层的软件层调用较高层中定义的子例程(或函数)。” -维基百科
使用函数指针在C中进行回调
在C语言中,回调是使用功能指针实现的。函数指针-顾名思义,它是指向函数的指针。
例如,int(* ptrFunc)();
在这里,ptrFunc是指向不带任何参数并返回整数的函数的指针。不要忘记加括号,否则编译器将假定ptrFunc是一个正常的函数名,它不执行任何操作并返回一个指向整数的指针。
这是一些代码来演示函数指针。
#include<stdio.h>
int func(int, int);
int main(void)
{
int result1,result2;
/* declaring a pointer to a function which takes
two int arguments and returns an integer as result */
int (*ptrFunc)(int,int);
/* assigning ptrFunc to func's address */
ptrFunc=func;
/* calling func() through explicit dereference */
result1 = (*ptrFunc)(10,20);
/* calling func() through implicit dereference */
result2 = ptrFunc(10,20);
printf("result1 = %d result2 = %d\n",result1,result2);
return 0;
}
int func(int x, int y)
{
return x+y;
}
现在让我们尝试使用函数指针来理解C语言中回调的概念。
完整的程序包含三个文件:callback.c,reg_callback.h和reg_callback.c。
/* callback.c */
#include<stdio.h>
#include"reg_callback.h"
/* callback function definition goes here */
void my_callback(void)
{
printf("inside my_callback\n");
}
int main(void)
{
/* initialize function pointer to
my_callback */
callback ptr_my_callback=my_callback;
printf("This is a program demonstrating function callback\n");
/* register our callback function */
register_callback(ptr_my_callback);
printf("back inside main program\n");
return 0;
}
/* reg_callback.h */
typedef void (*callback)(void);
void register_callback(callback ptr_reg_callback);
/* reg_callback.c */
#include<stdio.h>
#include"reg_callback.h"
/* registration goes here */
void register_callback(callback ptr_reg_callback)
{
printf("inside register_callback\n");
/* calling our callback function my_callback */
(*ptr_reg_callback)();
}
如果我们运行该程序,输出将是
这是一个程序,该程序在主程序内部的register_callback内部的my_callback内部演示函数回调
较高层的函数将较低层的函数作为普通调用来调用,并且回调机制允许较低层的函数通过指向回调函数的指针来调用较高层的函数。
Java使用界面中的回调
Java没有函数指针的概念。它通过其接口机制实现回调机制。在此,我们声明一个接口,该接口具有一个函数,该方法将在被调用者完成其任务时被调用,而不是函数指针。
让我通过一个示例进行演示:
回调接口
public interface Callback
{
public void notify(Result result);
}
呼叫者或更高级别的班级
public Class Caller implements Callback
{
Callee ce = new Callee(this); //pass self to the callee
//Other functionality
//Call the Asynctask
ce.doAsynctask();
public void notify(Result result){
//Got the result after the callee has finished the task
//Can do whatever i want with the result
}
}
被调用者或下层功能
public Class Callee {
Callback cb;
Callee(Callback cb){
this.cb = cb;
}
doAsynctask(){
//do the long running task
//get the result
cb.notify(result);//after the task is completed, notify the caller
}
}
使用EventListener模式进行回调
此模式用于通知0到n个观察者/侦听器特定任务已完成
回调机制和EventListener / Observer机制之间的区别在于,在回调中,被叫方通知单个调用方,而在Eventlisener / Observer中,被叫方可以通知对该事件感兴趣的任何人(该通知可能会转到该事件的其他部分。尚未触发任务的应用程序)
让我通过一个例子来解释它。
事件界面
public interface Events {
public void clickEvent();
public void longClickEvent();
}
类小部件
package com.som_itsolutions.training.java.exampleeventlistener;
import java.util.ArrayList;
import java.util.Iterator;
public class Widget implements Events{
ArrayList<OnClickEventListener> mClickEventListener = new ArrayList<OnClickEventListener>();
ArrayList<OnLongClickEventListener> mLongClickEventListener = new ArrayList<OnLongClickEventListener>();
@Override
public void clickEvent() {
// TODO Auto-generated method stub
Iterator<OnClickEventListener> it = mClickEventListener.iterator();
while(it.hasNext()){
OnClickEventListener li = it.next();
li.onClick(this);
}
}
@Override
public void longClickEvent() {
// TODO Auto-generated method stub
Iterator<OnLongClickEventListener> it = mLongClickEventListener.iterator();
while(it.hasNext()){
OnLongClickEventListener li = it.next();
li.onLongClick(this);
}
}
public interface OnClickEventListener
{
public void onClick (Widget source);
}
public interface OnLongClickEventListener
{
public void onLongClick (Widget source);
}
public void setOnClickEventListner(OnClickEventListener li){
mClickEventListener.add(li);
}
public void setOnLongClickEventListner(OnLongClickEventListener li){
mLongClickEventListener.add(li);
}
}
类按钮
public class Button extends Widget{
private String mButtonText;
public Button (){
}
public String getButtonText() {
return mButtonText;
}
public void setButtonText(String buttonText) {
this.mButtonText = buttonText;
}
}
类复选框
public class CheckBox extends Widget{
private boolean checked;
public CheckBox() {
checked = false;
}
public boolean isChecked(){
return (checked == true);
}
public void setCheck(boolean checked){
this.checked = checked;
}
}
活动课
包com.som_itsolutions.training.java.exampleeventlistener;
public class Activity implements Widget.OnClickEventListener
{
public Button mButton;
public CheckBox mCheckBox;
private static Activity mActivityHandler;
public static Activity getActivityHandle(){
return mActivityHandler;
}
public Activity ()
{
mActivityHandler = this;
mButton = new Button();
mButton.setOnClickEventListner(this);
mCheckBox = new CheckBox();
mCheckBox.setOnClickEventListner(this);
}
public void onClick (Widget source)
{
if(source == mButton){
mButton.setButtonText("Thank you for clicking me...");
System.out.println(((Button) mButton).getButtonText());
}
if(source == mCheckBox){
if(mCheckBox.isChecked()==false){
mCheckBox.setCheck(true);
System.out.println("The checkbox is checked...");
}
else{
mCheckBox.setCheck(false);
System.out.println("The checkbox is not checked...");
}
}
}
public void doSomeWork(Widget source){
source.clickEvent();
}
}
其他类
public class OtherClass implements Widget.OnClickEventListener{
Button mButton;
public OtherClass(){
mButton = Activity.getActivityHandle().mButton;
mButton.setOnClickEventListner(this);//interested in the click event //of the button
}
@Override
public void onClick(Widget source) {
if(source == mButton){
System.out.println("Other Class has also received the event notification...");
}
}
主班
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Activity a = new Activity();
OtherClass o = new OtherClass();
a.doSomeWork(a.mButton);
a.doSomeWork(a.mCheckBox);
}
}
从上面的代码中可以看到,我们有一个称为事件的接口,该接口基本上列出了应用程序可能发生的所有事件。Widget类是所有UI组件(如Button,Checkbox)的基类。这些UI组件是实际从框架代码接收事件的对象。窗口小部件类实现事件接口,并且它具有两个嵌套接口,即OnClickEventListener和OnLongClickEventListener
这两个接口负责侦听在Widget派生的UI组件(例如Button或Checkbox)上可能发生的事件。因此,如果将本示例与使用Java接口的早期Callback示例进行比较,则这两个接口将用作Callback接口。因此,较高级别的代码(此处的活动)实现了这两个接口。并且,每当窗口小部件发生事件时,就会调用更高级别的代码(或在更高级别的代码中实现的这些接口的方法,在这里为Activity)。
现在让我讨论Callback和Eventlistener模式之间的基本区别。正如我们已经提到的,使用回叫,被叫方只能通知一个呼叫者。但是,对于EventListener模式,应用程序的任何其他部分或类都可以注册按钮或复选框上可能发生的事件。此类的示例是OtherClass。如果您看到OtherClass的代码,则会发现它已将自己注册为ClickEvent的侦听器,该事件可能在Activity中定义的Button中发生。有趣的是,除了活动(调用方)之外,只要在Button上发生click事件,此OtherClass也会被通知。
回调允许您将自己的代码插入到另一个代码块中,以在另一时间执行,这可以修改或增加其他代码块的行为,以满足您的需求。您可以获得灵活性和可定制性,同时能够拥有更多可维护的代码。
更少的硬编码=易于维护和更改=更少的时间=更多的商业价值=令人敬畏。
例如,在JavaScript中,使用Underscore.js,您可以在数组中找到所有偶数元素,如下所示:
var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> [2, 4, 6]
示例由Underscore.js提供:http : //documentcloud.github.com/underscore/#filter
将方法视为将任务交给同事的方法。一个简单的任务可能如下:
Solve these equations:
x + 2 = y
2 * x = 3 * y
您的同事努力地进行数学运算,并为您提供以下结果:
x = -6
y = -4
但是您的同事有一个问题,他并不总是理解诸如的符号^
,但是他确实通过它们的描述来理解它们。如exponent
。每当他发现其中之一时,您都会得到以下信息:
I don't understand "^"
这要求您在向同事解释了角色的含义之后再次重写整个指令集,而他并不总是记住在两次提问之间。而且他也很难记住您的提示,例如问我。但是,他始终尽您所能遵循您的书面指示。
您想到了一个解决方案,只需将以下内容添加到所有说明中:
If you have any questions about symbols, call me at extension 1234 and I will tell you its name.
现在,每当他遇到问题时,他都会打电话给您并询问,而不是给您不好的答复并使流程重新开始。
就下载网页而言:
您的程序在手机上运行,并请求http://www.google.com网页。如果您同步编写程序,则为下载数据而编写的功能将持续运行,直到下载所有数据为止。这意味着您的用户界面不会刷新,并且基本上会冻结。如果使用回调编写程序,则会请求数据并说“完成后执行此函数”。这允许UI在文件下载时仍然允许用户交互。网页下载完成后,将调用结果函数(回调),您可以处理数据。
基本上,它允许您请求某些内容并在等待结果时继续执行。一旦通过回调函数将结果返回给您,您就可以在中断处进行操作。