如何用简单的英语解释回调?它们与从一个函数调用另一个函数从调用函数获取上下文有什么不同?如何向新手程序员解释它们的强大功能?


当前回答

A callback is a function that will be called by a second function. This second function doesn't know in advance what function it will call. So the identity of the callback function is stored somewhere, or passed to the second function as a parameter. This "identity," depending on the programming language, might be the address of the callback, or some other sort of pointer, or it might be the name of the function. The principal is the same, we store or pass some information that unambiguously identifies the function.

当时间到来时,第二个函数可以调用回调,根据当时的情况提供参数。它甚至可以从一组可能的回调中选择回调。编程语言必须提供某种语法,允许第二个函数调用回调函数,并知道它的“标识”。

这种机制有许多可能的用途。通过回调,函数的设计者可以通过调用所提供的任何回调来定制函数。例如,排序函数可能将回调函数作为参数,而这个回调函数可能是用于比较两个元素以决定哪个元素先出现的函数。

顺便说一下,根据编程语言的不同,上面讨论中的“函数”一词可能会被“块”、“闭包”、“lambda”等取代。

其他回答

在PHP中,它是这样的:

<?php

function string($string, $callback) {
     $results = array(
        'upper' => strtoupper($string),
        'lower' => strtolower($string),
        );

    if(is_callable($callback)) {
        call_user_func($callback, $results);
    }
}

string('Alex', function($name) {
    echo $name['lower'];
});

想象一下,一个朋友要离开你的家,你告诉她“到家后给我打个电话,好让我知道你已经安全到家了”;这是(字面上的)回电。这就是回调函数,与语言无关。您希望某个过程在完成某些任务后将控制传递回给您,因此您可以给它一个函数,用于回调。

例如,在Python中,

grabDBValue( (lambda x: passValueToGUIWindow(x) ))

grabDBValue可以被编写为只从数据库获取一个值,然后让您指定对该值实际做什么,因此它接受一个函数。您不知道grabDBValue何时或是否会返回,但是如果/当它返回时,您知道希望它做什么。在这里,我传入一个匿名函数(或lambda),它将值发送到GUI窗口。我可以通过这样做轻松地改变程序的行为:

grabDBValue( (lambda x: passToLogger(x) ))

回调在函数是第一类值的语言中工作得很好,就像通常的整数、字符串、布尔值等。在C语言中,你可以通过传递指向函数的指针来“传递”函数,调用者可以使用它;在Java中,调用者将请求具有特定方法名的特定类型的静态类,因为类之外没有函数(实际上是“方法”);在大多数其他动态语言中,您可以通过简单的语法传递函数。

Protip:

在具有词法作用域的语言(如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,因为回调可以访问定义它的词法环境中声明的变量。词法作用域和匿名回调是一个强大的组合,值得新手进一步研究。

这是关于下载网页的:

Your program runs on a cellphone and is requesting the webpage http://www.google.com. If you write your program synchronously, the function you write to download the data will be running continuously until all the data is download. This means your UI will not refresh and will basically appear frozen. If you write your program using callbacks, you request the data and say "execute this function when you've finished." This allows the UI to still allow user interaction while the file is downloading. Once the webpage has finished downloading, your result function (callback) is called and you can handle the data.

基本上,它允许你请求一些东西,并在等待结果的同时继续执行。一旦结果通过回调函数返回给您,您就可以从它停止的地方开始操作。

这里有两点需要解释,一个是回调是如何工作的(传递一个可以在不了解上下文的情况下调用的函数),另一个是它的用途(异步处理事件)。

用其他答案用过的等待包裹到达的比喻来解释这两个问题是很好的。在计算机程序中,你会告诉计算机期待一个包裹。通常情况下,它现在会坐在那里等待(什么也不做),直到包裹到达,如果包裹从未到达,它可能会无限期地等待。对人类来说,这听起来很愚蠢,但如果没有进一步的措施,这对计算机来说是完全自然的。

现在回调是你前门的铃声。你为包裹服务提供了一种通知你包裹到达的方式,而不需要他们知道你在房子的哪里(即使),或者铃声是如何工作的。(例如,一些“铃声”实际上是发送一个电话。)因为你提供了一个“回调函数”,可以在任何时候被“调用”,脱离上下文,你现在可以不用坐在门廊前,随时“处理事件”(包裹到达)。

在计算机编程中,回调是对可执行代码或一段可执行代码的引用,它作为参数传递给其他代码。这允许较低级别的软件层调用较高级别的软件层定义的子例程(或函数)。——维基百科

在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;
}
}

Activity类

包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);
}
}

从上面的代码中可以看到,我们有一个名为events的接口,它基本上列出了应用程序可能发生的所有事件。Widget类是所有UI组件(如按钮、复选框)的基类。这些UI组件是实际从框架代码接收事件的对象。Widget类实现了Events接口,它也有两个嵌套接口,即OnClickEventListener和OnLongClickEventListener

These two interfaces are responsible for listening to events that may occur on the Widget derived UI components like Button or Checkbox. So if we compare this example with the earlier Callback example using Java Interface, these two interfaces work as the Callback interface. So the higher level code (Here Activity) implements these two interfaces. And whenever an event occurs to a widget, the higher level code (or the method of these interfaces implemented in the higher level code, which is here Activity) will be called.

Now let me discuss the basic difference between Callback and Eventlistener pattern. As we have mentioned that using Callback, the Callee can notify only a single Caller. But in the case of EventListener pattern, any other part or class of the Application can register for the events that may occur on the Button or Checkbox. The example of this kind of class is the OtherClass. If you see the code of the OtherClass, you will find that it has registered itself as a listener to the ClickEvent that may occur in the Button defined in the Activity. Interesting part is that, besides the Activity ( the Caller), this OtherClass will also be notified whenever the click event occurs on the Button.