Yah*_*-Me 334 language-agnostic terminology function callback
如何用简单的英语解释回调?它们与从调用函数中获取某些上下文的另一个函数调用一个函数有什么不同?如何向新手程序员解释他们的权力?
Jos*_*off 518
我将尽力保持这种简单."回调"是由另一个函数调用的任何函数,该函数将第一个函数作为参数.很多的时候,一个"回调"是一个时被调用函数事情发生.在程序员说话中,这个东西可以被称为"事件".
想象一下这种情况:您期待在几天内获得一个包.包裹是给邻居的礼物.因此,一旦你得到包裹,你想要它带到邻居.你不在城里,所以你要为你的配偶留下指示.
您可以告诉他们获取包裹并将其带给邻居.如果你的配偶和计算机一样愚蠢,他们会坐在门口等待包裹直到它来了(没有做任何事情)然后一旦它来了,他们就会把它带到邻居家.但是有更好的方法.告诉你的配偶,一旦他们收到包裹,他们应该把它带到邻居家.然后,他们可以正常生活,直到他们收到包裹.
在我们的例子中,包的接收是"事件",并且将它带到邻居是"回调".您的配偶"运行"您的指示,仅在包裹到达时将包裹带过来.好多了!
这种想法在日常生活中是显而易见的,但计算机没有同样的常识.考虑一下程序员通常如何写入文件:
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
Run Code Online (Sandbox Code Playgroud)
在这里,我们在写入之前等待文件打开.这会"阻止"执行流程,而我们的程序无法执行其可能需要执行的任何其他操作!如果我们能做到这一点怎么办:
# 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!
Run Code Online (Sandbox Code Playgroud)
事实证明,我们使用一些语言和框架来做到这一点.它太酷了!查看Node.js以获得这种思考的真实练习.
Nir*_*nit 113
应用程序通常需要根据其上下文/状态执行不同的功能.为此,我们使用一个变量来存储有关要调用的函数的信息.根据需要,应用程序将使用有关要调用的函数的信息设置此变量,并使用相同的变量调用该函数.
在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]
Run Code Online (Sandbox Code Playgroud)
use*_*994 82
如何用简单的英语解释回调?
简单来说,回调函数就像一个工人,当他完成一个任务时"回调"给他的经理.
它们与从调用函数中获取某些上下文的另一个函数调用一个函数有什么不同?
确实,您正在从另一个函数调用函数,但关键是回调被视为一个Object,因此您可以根据系统状态(如策略设计模式)更改要调用的函数.
如何向新手程序员解释他们的权力?
在需要从服务器提取数据的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)');
}
Run Code Online (Sandbox Code Playgroud)
以下是使用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
}
Run Code Online (Sandbox Code Playgroud)
回调通常需要state
使用a 来从调用函数访问closure
,这就像Worker需要在完成任务之前从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
}
Run Code Online (Sandbox Code Playgroud)
// 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);
Run Code Online (Sandbox Code Playgroud)
最后,这里是一个定义closure
从道格拉斯·克罗克福德:
可以在其他函数内定义函数.内部函数可以访问外部函数的变量和参数.如果对内部函数的引用存活(例如,作为回调函数),则外部函数的变量也存活.
也可以看看:
小智 39
我惊呆了,看到这么多聪明人没有强调"回调"这个词已经以两种不一致的方式使用的现实.
两种方式都涉及通过将附加功能(函数定义,匿名或命名)传递给现有函数来定制函数.即.
customizableFunc(customFunctionality)
Run Code Online (Sandbox Code Playgroud)
如果只是将自定义功能插入到代码块中,那么您已经自定义了该功能,就像这样.
customizableFucn(customFunctionality) {
var data = doSomthing();
customFunctionality(data);
...
}
Run Code Online (Sandbox Code Playgroud)
虽然这种注入功能通常被称为"回调",但没有任何条件.一个非常明显的例子是forEach方法,其中自定义函数作为参数提供,以应用于数组中的每个元素以修改数组.
但这基本上不同于使用"回调"函数进行异步编程,如AJAX或node.js,或仅仅是为用户交互事件(如鼠标点击)分配功能.在这种情况下,整个想法是在执行自定义功能之前等待偶然事件发生.这在用户交互的情况下是显而易见的,但在可能需要时间的I/O(输入/输出)进程中也很重要,例如从磁盘读取文件.这就是"回调"一词最明显的含义.一旦启动了i/o进程(比如要求从磁盘或服务器读取文件以从http请求返回数据)异步程序不会等待它完成.它可以继续执行接下来安排的任何任务,并且仅在通知读取文件或http请求已完成(或失败)并且数据可用于自定义功能之后才响应自定义功能.这就像打电话给企业并留下你的"回拨号码",这样他们就可以在有人回复你的时候打电话给你.这比谁知道谁知道多久而且无法处理其他事务更好.
异步使用固有地涉及一些监听所需事件的方法(例如,完成I/O过程),以便当它发生时(并且仅当它发生时)执行自定义"回调"功能.在明显的AJAX示例中,当数据实际从服务器到达时,触发"回调"函数以使用该数据来修改DOM,从而将浏览器窗口重绘到该范围.
回顾一下.有些人使用"回调"一词来指代可以作为参数注入现有函数的任何类型的自定义功能.但是,至少在我看来,最合适的用法是将注入的"回调"函数异步使用 - 只有在等待通知的事件发生时才执行.
小智 26
在非程序员的术语中,回调是程序中的空白填充.
许多纸质表格上的一个共同项目是"紧急情况下的人员".那里有一个空白行.你用某人的姓名和电话号码写下来.如果发生紧急情况,那么该人就会被召唤.
这是关键.你不要改变形式(代码,通常是别人的代码).但是,您可以填写缺失的信息(您的号码).
例1:
回调用作自定义方法,可能用于添加/更改程序的行为.例如,使用一些执行函数的C代码,但不知道如何打印输出.它所能做的只是制作一个字符串.当它试图找出如何处理字符串时,它会看到一个空行.但是,程序员给你留空写回调!
在此示例中,您不使用铅笔在一张纸上填充空白,而是使用该功能set_print_callback(the_callback)
.
set_print_callback
是铅笔,the_callback
是您填写的信息.您现在已在程序中填写此空白行.每当需要打印输出时,它将查看该空行,并按照那里的说明进行操作(即调用放在那里的功能.)实际上,这允许打印到屏幕,日志文件,打印机,通过网络连接或其任何组合.你已经填写了你想做的空白.
例2:
当您被告知需要拨打紧急号码时,您可以阅读纸质表格上的内容,然后拨打您阅读的号码.如果该行为空白,则不会执行任何操作.
Gui编程的工作方式大致相同.单击按钮时,程序需要确定下一步操作.它去寻找回调.这个回调恰好在一个空白处标有"这就是你点击Button1时所做的事情"
当你提出要求时,大多数IDE会自动填写空白(写基本方法)(例如button1_clicked
).但是,这个空白可以有任何方法,你请努力.您可以调用该方法,run_computations
或者butter_the_biscuits
只要将该回调的名称放在正确的空白处即可.您可以将紧急号码中的"555-555-1212"留空.它没有多大意义,但它是允许的.
最后的注意事项:您正在填写回调的空白行?它可以随意擦除和重写.(无论你是否应该是另一个问题,但这是他们权力的一部分)
tov*_*eod 21
程序员Johny需要一台订书机,所以他下到办公室供应部门要求一个,在填写申请表之后,他可以站在那里等待店员去仓库看看订书机(就像一个阻塞功能电话) )或者去做其他的事情.
因为这通常需要时间,所以johny将请求表格与请求表格放在一起,要求他们在订书机准备好接听时给他打电话,同时他可以去做其他事情,比如在桌子上打盹.
小智 19
总是更好地开始一个例子:).
我们假设您有两个模块A和B.
您希望在模块B中发生某些事件/条件时通知模块A.但是,模块B不知道您的模块A.它只知道通过函数指针(模块A)的特定函数的地址.由模块A提供给它.
所以B现在必须做的是,当使用函数指针发生特定事件/条件时,将"回调"到模块A中.A可以在回调函数内部进行进一步处理.
*)这里的一个明显优势是你从模块B中抽象出模块A的所有内容.模块B不必关心模块A是谁/什么模块.
Bri*_*kel 18
想象一下,你需要一个返回10平方的函数,所以你写一个函数:
function tenSquared() {return 10*10;}
Run Code Online (Sandbox Code Playgroud)
之后你需要9平方,所以你写另一个函数:
function nineSquared() {return 9*9;}
Run Code Online (Sandbox Code Playgroud)
最终,您将使用通用函数替换所有这些:
function square(x) {return x*x;}
Run Code Online (Sandbox Code Playgroud)
完全相同的想法适用于回调.你有一个功能可以执行某些操作,完成后调用doA:
function computeA(){
...
doA(result);
}
Run Code Online (Sandbox Code Playgroud)
稍后你想要完全相同的函数来调用doB而不是你可以复制整个函数:
function computeB(){
...
doB(result);
}
Run Code Online (Sandbox Code Playgroud)
或者你可以将回调函数作为变量传递,只需要有一次函数:
function compute(callback){
...
callback(result);
}
Run Code Online (Sandbox Code Playgroud)
然后你只需要调用compute(doA)和compute(doB).
除了简化代码之外,它还允许异步代码通过在完成时调用您的任意函数来让您知道它已经完成,类似于您在电话上呼叫某人并留下回拨号码时.
Han*_*etz 10
有两点需要解释,一个是回调是如何工作的(传递一个可以在不知道其上下文的情况下调用的函数),另一个是它用于(异步处理事件).
等待包裹到达的类比已被其他答案使用,这是一个很好的解释两者.在计算机程序中,您会告诉计算机需要一个包裹.通常,它现在会坐在那里等待(并且不做任何其他事情),直到包裹到达,如果它永远不会到达,可能会无限期地.对于人类而言,这听起来很愚蠢,但如果没有进一步的措施,这对计算机来说是完全自然的
现在回调将是你前门的钟声.您向包裹服务部门提供了一种方式来通知您包裹的到达,而无需知道您在哪里(即使您)在房子里,或者铃声是如何工作的.(例如,一些"铃声"实际上是在拨打电话.)因为你提供了一个"回叫功能",可以随时"调用",脱离上下文,你现在可以停在前廊并"处理事件"(包裹到达)无论什么时候.
这是我自己生活中的一个真实例子。
当我今天下午 5 点完成工作时,我的待办事项清单上有很多事情:
当我打电话给兽医时,接待员正在打电话。接待员告诉我,我需要等待兽医来,以便兽医可以向我解释测试结果。接待员想让我等一下,直到兽医准备好。
你对此有何反应?我知道我的:多么低效!所以我向接待员提议让兽医给我回电话在她准备好说话时这样,我就可以处理其他任务,而不必等待电话。然后,当兽医准备好时,我可以暂停其他任务并与她交谈。
我是单线程的。我一次只能做一件事。如果我是多线程的,我就能够并行处理多个任务,但不幸的是,我不能这样做。
如果没有回调,当我遇到异步任务时,它就会阻塞。例如。当我打电话给兽医时,兽医需要花大约 15 分钟才能完成她正在做的事情,然后才能与我交谈。如果没有回电,我在这 15 分钟内就会被屏蔽。我只能坐下来等待,而不是去处理其他任务。
以下是没有回调的代码的外观:
function main() {
callVet();
// blocked for 15 minutes
walkDog();
doTaxes();
doDishes();
answerPeronalEmails();
doLaundry();
}
Run Code Online (Sandbox Code Playgroud)
现在有了回调:
function main() {
callVet(function vetCallback(vetOnThePhoneReadyToSpeakWithMe) {
talkToVetAboutTestResults(vetOnThePhoneReadyToSpeakWithMe);
});
walkDog();
doTaxes();
doDishes();
answerPeronalEmails();
doLaundry();
}
Run Code Online (Sandbox Code Playgroud)
更一般地说,当您处于单线程执行环境中并且有某种异步任务时,您可以使用回调以更符合逻辑的顺序执行事物,而不是让该任务阻塞您的单线程。
一个很好的例子是,如果您有一些前端代码需要发出 ajax 请求。例如。如果您有一个显示有关用户信息的仪表板。以下是在没有回调的情况下它的工作方式。用户会立即看到导航栏,但他们必须等待一段时间才能看到侧边栏和页脚,因为 ajax 请求getUser
需要一段时间(根据经验,网络被认为很慢)。
function main() {
displayNavbar();
const user = getUser();
// wait a few seconds for response...
displayUserDashboard(user);
displaySidebar();
displayFooter();
}
Run Code Online (Sandbox Code Playgroud)
现在有了回调:
function main() {
displayNavbar();
getUser(function (user) {
displayUserDashboard(user);
});
displaySidebar();
displayFooter();
}
Run Code Online (Sandbox Code Playgroud)
通过利用回调,我们现在可以在 ajax 请求的响应返回给我们之前显示侧边栏和页脚。这类似于我对接待员说:“我不想在电话上等 15 分钟。当兽医准备好与我交谈时给我回电话,同时我将继续处理其他事情我的待办事项清单。” 在现实生活中,你可能应该更加优雅一点,但在编写软件时,你可以对 CPU 尽可能粗鲁。
小智 6
想象一下,一位朋友正在离开你的房子,你告诉她"当你回到家时给我打电话,以便我知道你安全到达了"; 这是(字面意思)一个回电.这就是回调函数,无论语言如何.你想要一些程序在完成某项任务时将控制权交还给你,所以你给它一个函数来回调你.
例如,在Python中,
grabDBValue( (lambda x: passValueToGUIWindow(x) ))
Run Code Online (Sandbox Code Playgroud)
grabDBValue
可以写入只从数据库中获取一个值,然后让你指定实际对该值做什么,所以它接受一个函数.您不知道何时或是否grabDBValue
会返回,但如果/何时返回,您知道您希望它做什么.在这里,我传入一个匿名函数(或lambda),它将值发送到GUI窗口.我可以通过这样做轻松地改变程序的行为:
grabDBValue( (lambda x: passToLogger(x) ))
Run Code Online (Sandbox Code Playgroud)
回调在函数是第一类值的语言中运行良好,就像通常的整数,字符串,布尔值等一样.在C中,你可以通过传递指向它的指针来"传递"一个函数,调用者可以使用它; 在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
Run Code Online (Sandbox Code Playgroud)
$val
在这种情况下,6
因为回调可以访问在词法环境中声明的变量.词汇范围和匿名回调是一个强大的组合,值得进一步研究新手程序员.
您有一些想要运行的代码.通常情况下,当你调用它时,你正在等待它继续运行之前(这可能会导致你的应用程序变灰/产生光标的旋转时间).
另一种方法是并行运行此代码并继续您自己的工作.但是,如果你的原始代码需要根据它调用的代码的响应做不同的事情呢?那么,在这种情况下,你可以传入你希望它在完成后调用的代码的名称/位置.这是一个"回电".
正常代码:询问信息 - >流程信息 - >处理处理结果 - >继续执行其他操作.
回调:询问信息 - >过程信息 - >继续做其他事情.在稍后的某个时候 - >处理Processing的结果.
如果没有其他特殊编程资源(如线程等)的回调,程序就是一个接一个地顺序执行的指令序列,甚至是某种条件决定的"动态行为",所有可能的场景应事先编程.
因此,如果我们需要为程序提供真正的动态行为,我们可以使用回调.使用回调,您可以通过参数指示,程序调用另一个程序提供一些先前定义的参数并且可以预期一些结果(这是合同或操作签名),因此这些结果可以由第三方程序生成/处理以前不知道.
这种技术是应用于程序,函数,对象以及计算机运行的所有其他代码的多态性的基础.
当你做一些工作时,用人类世界作为回调的例子是很好的解释,让我们假设你是一个画家(在这里你是主要的程序,画画),并打电话给你的客户有时要求他批准你的工作结果因此,他决定图片是否良好(您的客户是第三方程序).
在上面的例子中,你是一个画家,并"委托"其他人批准结果的工作,图片是参数,每个新客户(回调"功能")改变你的工作决定他想要的结果关于图片(客户做出的决定是"回调函数"的返回结果).
我希望这个解释有用.
让我们假装你给我一个潜在的长期任务:获得你遇到的前五个独特人物的名字.如果我在人烟稀少的地区,这可能需要几天时间.当我跑来跑去的时候,你真的没有兴趣坐在你的手上,所以你说,"当你拿到名单时,请打电话给我,然后把它读回给我.这是数字."
你给了我一个回调参考 - 我应该执行的一个函数,以便进行进一步的处理.
在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);
Run Code Online (Sandbox Code Playgroud)
这可能会以很多方式得到改善.例如,你可以提供第二个回调:如果它最终花费超过一个小时,请拨打红色电话并告诉那个人你已经超时的答案.
根据电话系统最容易描述回叫.功能调用类似于通过电话呼叫某人,向她询问问题,获得答案以及挂断电话; 添加回调更改了类比,以便在向她询问问题后,您还会告诉她您的姓名和号码,以便她可以给您回复答案. - Paul Jakubik,"C++中的回调实现"
\xe2\x80\x9c在计算机编程中,回调是对可执行代码或一段可执行代码的引用,它作为参数传递给其他代码。这允许较低级别的软件层调用较高级别层中定义的子例程(或函数)。\xe2\x80\x9d - 维基百科
\n\nC 中使用函数指针的回调
\n\n在C语言中,回调是使用函数指针来实现的。函数指针——顾名思义,是指向函数的指针。
\n\n例如 int (*ptrFunc) ();
\n\n这里,ptrFunc 是一个指向函数的指针,该函数不带参数并返回一个整数。不要忘记放入括号,否则编译器会假设 ptrFunc 是一个普通的函数名称,它不接受任何内容并返回一个指向整数的指针。
\n\n这是一些演示函数指针的代码。
\n\n#include<stdio.h>\nint func(int, int);\nint main(void)\n{\n int result1,result2;\n /* declaring a pointer to a function which takes\n two int arguments and returns an integer as result */\n int (*ptrFunc)(int,int);\n\n /* assigning ptrFunc to func\'s address */ \n ptrFunc=func;\n\n /* calling func() through explicit dereference */\n result1 = (*ptrFunc)(10,20);\n\n /* calling func() through implicit dereference */ \n result2 = ptrFunc(10,20); \n printf("result1 = %d result2 = %d\\n",result1,result2);\n return 0;\n}\n\nint func(int x, int y)\n{\n return x+y;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n现在让我们尝试使用函数指针来理解 C 中回调的概念。
\n\n完整的程序包含三个文件:callback.c、reg_callback.h 和 reg_callback.c。
\n\n/* callback.c */\n#include<stdio.h>\n#include"reg_callback.h"\n\n/* callback function definition goes here */\nvoid my_callback(void)\n{\n printf("inside my_callback\\n");\n}\n\nint main(void)\n{\n /* initialize function pointer to\n my_callback */\n callback ptr_my_callback=my_callback; \n printf("This is a program demonstrating function callback\\n");\n /* register our callback function */\n register_callback(ptr_my_callback); \n printf("back inside main program\\n");\n return 0;\n}\n\n/* reg_callback.h */\ntypedef void (*callback)(void);\nvoid register_callback(callback ptr_reg_callback);\n\n\n/* reg_callback.c */\n#include<stdio.h>\n#include"reg_callback.h"\n\n/* registration goes here */\nvoid register_callback(callback ptr_reg_callback)\n{\n printf("inside register_callback\\n");\n /* calling our callback function my_callback */\n (*ptr_reg_callback)(); \n}\n
Run Code Online (Sandbox Code Playgroud)\n\n如果我们运行这个程序,输出将是
\n\n这是一个演示主程序内函数回调\内部register_callback\内部my_callback\nback的程序
\n\n上层函数调用下层函数就像普通调用一样,回调机制允许下层函数通过指向回调函数的指针来调用上层函数。
\n\nJava中使用接口回调
\n\nJava没有函数指针的概念\n它通过接口机制实现回调机制\n这里我们声明的不是函数指针,而是一个具有方法的接口,当被调用者完成其任务时将调用该方法
\n\n让我通过一个例子来演示一下:
\n\n回调接口
\n\npublic interface Callback\n{\n public void notify(Result result);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n调用者或更高级别的类
\n\npublic Class Caller implements Callback\n{\nCallee ce = new Callee(this); //pass self to the callee\n\n//Other functionality\n//Call the Asynctask\nce.doAsynctask();\n\npublic void notify(Result result){\n//Got the result after the callee has finished the task\n//Can do whatever i want with the result\n}\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n被调用者或下层函数
\n\npublic Class Callee {\nCallback cb;\nCallee(Callback cb){\nthis.cb = cb;\n}\n\ndoAsynctask(){\n//do the long running task\n//get the result\ncb.notify(result);//after the task is completed, notify the caller\n}\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n使用事件监听器模式进行回调
\n\n此模式用于通知 0 到 n 个观察者/监听者特定任务已完成
\n\nCallback 机制和 EventListener/Observer 机制之间的区别在于,在回调中,被调用者通知单个调用者,而在 Eventlisener/Observer 中,被调用者可以通知任何对该事件感兴趣的人(通知可能会发送到事件的其他部分)尚未触发任务的应用程序)
\n\n让我通过一个例子来解释一下。
\n\n事件接口
\n\npublic interface Events {\n\npublic void clickEvent();\npublic void longClickEvent();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n类小部件
\n\npackage com.som_itsolutions.training.java.exampleeventlistener;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\n\npublic class Widget implements Events{\n\n ArrayList<OnClickEventListener> mClickEventListener = new ArrayList<OnClickEventListener>(); \n ArrayList<OnLongClickEventListener> mLongClickEventListener = new ArrayList<OnLongClickEventListener>();\n\n @Override\n public void clickEvent() {\n // TODO Auto-generated method stub\n Iterator<OnClickEventListener> it = mClickEventListener.iterator();\n while(it.hasNext()){\n OnClickEventListener li = it.next();\n li.onClick(this);\n } \n }\n @Override\n public void longClickEvent() {\n // TODO Auto-generated method stub\n Iterator<OnLongClickEventListener> it = mLongClickEventListener.iterator();\n while(it.hasNext()){\n OnLongClickEventListener li = it.next();\n li.onLongClick(this);\n }\n\n }\n\n public interface OnClickEventListener\n {\n public void onClick (Widget source);\n }\n\n public interface OnLongClickEventListener\n {\n public void onLongClick (Widget source);\n }\n\n public void setOnClickEventListner(OnClickEventListener li){\n mClickEventListener.add(li);\n }\n public void setOnLongClickEventListner(OnLongClickEventListener li){\n mLongClickEventListener.add(li);\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n班级按钮
\n\npublic class Button extends Widget{\nprivate String mButtonText;\npublic Button (){\n} \npublic String getButtonText() {\nreturn mButtonText;\n}\npublic void setButtonText(String buttonText) {\nthis.mButtonText = buttonText;\n}\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n类复选框
\n\npublic class CheckBox extends Widget{\nprivate boolean checked;\npublic CheckBox() {\nchecked = false;\n}\npublic boolean isChecked(){\nreturn (checked == true);\n}\npublic void setCheck(boolean checked){\nthis.checked = checked;\n}\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n活动课
\n\n包 com.som_itsolutions.training.java.exampleeventlistener;
\n\npublic class Activity implements Widget.OnClickEventListener\n{\n public Button mButton;\n public CheckBox mCheckBox;\n private static Activity mActivityHandler;\n public static Activity getActivityHandle(){\n return mActivityHandler;\n }\n public Activity ()\n {\n mActivityHandler = this;\n mButton = new Button();\n mButton.setOnClickEventListner(this);\n mCheckBox = new CheckBox();\n mCheckBox.setOnClickEventListner(this);\n } \n public void onClick (Widget source)\n {\n if(source == mButton){\n mButton.setButtonText("Thank you for clicking me...");\n System.out.println(((Button) mButton).getButtonText());\n }\n if(source == mCheckBox){\n if(mCheckBox.isChecked()==false){\n mCheckBox.setCheck(true);\n System.out.println("The checkbox is checked...");\n }\n else{\n mCheckBox.setCheck(false);\n System.out.println("The checkbox is not checked...");\n } \n }\n }\n public void doSomeWork(Widget source){\n source.clickEvent();\n } \n}\n
Run Code Online (Sandbox Code Playgroud)\n\n其他类
\n\npublic class OtherClass implements Widget.OnClickEventListener{\nButton mButton;\npublic OtherClass(){\nmButton = Activity.getActivityHandle().mButton;\nmButton.setOnClickEventListner(this);//interested in the click event //of the button\n}\n@Override\npublic void onClick(Widget source) {\nif(source == mButton){\nSystem.out.println("Other Class has also received the event notification...");\n}\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n主班
\n\npublic class Main {\npublic static void main(String[] args) {\n// TODO Auto-generated method stub\nActivity a = new Activity();\nOtherClass o = new OtherClass();\na.doSomeWork(a.mButton);\na.doSomeWork(a.mCheckBox);\n}\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n从上面的代码中可以看出,我们有一个名为 events 的接口,它基本上列出了我们的应用程序可能发生的所有事件。Widget 类是所有 UI 组件(如 Button、Checkbox)的基类。这些 UI 组件是实际从框架代码接收事件的对象。Widget 类实现了 Events 接口,并且它有两个嵌套接口,即 OnClickEventListener 和 OnLongClickEventListener
\n\n这两个接口负责监听 Widget 派生 UI 组件(如 Button 或 Checkbox)上可能发生的事件。因此,如果我们将此示例与之前使用 Java 接口的 Callback 示例进行比较,就会发现这两个接口充当 Callback 接口。所以更高层的代码(这里是Activity)实现了这两个接口。而每当一个widget发生事件时,高层代码(或者高层代码中实现的这些接口的方法,这里就是Activity)就会被调用。
\n\n现在让我讨论回调和事件监听器模式之间的基本区别。正如我们所提到的,使用回调,被调用者只能通知单个调用者。但在事件监听器模式的情况下,应用程序的任何其他部分或类都可以注册按钮或复选框上可能发生的事件。这种类的例子是OtherClass。如果你看到OtherClass的代码,你会发现它已经将自己注册为Activity中定义的Button中可能发生的ClickEvent的监听器。有趣的是,除了 Activity(调用者)之外,每当 Button 上发生单击事件时,这个 OtherClass 也会收到通知。
\n通常我们将变量发送给函数.假设你有一个任务需要在作为参数给出之前处理变量 - 你可以使用回调.
function1(var1, var2)
是通常的方式.
如果我想要var2
处理然后作为参数发送怎么办?
function1(var1, function2(var2))
这是一种类型的回调 - function2
执行一些代码并将变量返回给初始函数.
为了教授回调,你必须先教授指针。一旦学生理解了指向变量的指针的概念,回调的概念就会变得更容易。假设您使用的是 C/C++,则可以遵循这些步骤。
可能还有很多东西。让学生参与,他们会发现。希望这可以帮助。
回调是一个将由第二个函数调用的函数.第二个函数事先不知道它将调用什么函数.因此,回调函数的标识存储在某处,或作为参数传递给第二个函数.这种"身份",取决于编程语言,可能是回调的地址,或某种其他类型的指针,或者它可能是函数的名称.主体是相同的,我们存储或传递一些明确标识功能的信息.
到时,第二个函数可以调用回调,根据当时的情况提供参数.它甚至可能从一组可能的回调中选择回调.编程语言必须提供某种语法,以允许第二个函数调用回调,知道它的"身份".
这种机制有很多可能的用途.通过回调,函数的设计者可以通过调用所提供的任何回调来对其进行自定义.例如,排序函数可能将回调作为参数,并且此回调可能是用于比较两个元素以确定哪个元素首先出现的函数.
顺便说一句,根据编程语言,上面讨论中的"函数"一词可能被"块","闭包","lambda"等替换.