Wie kann man Rückrufe in einfachem Englisch erklären? Wie unterscheiden sie sich vom Aufruf einer Funktion aus einer anderen Funktion, die einen Kontext von der aufrufenden Funktion übernimmt? Wie kann ihre Leistungsfähigkeit einem unerfahrenen Programmierer erklärt werden?
Antworten
Zu viele Anzeigen?"In der Computerprogrammierung ist ein Rückruf ein Verweis auf einen ausführbaren Code oder einen Teil eines ausführbaren Codes, der als Argument an einen anderen Code übergeben wird. Dadurch kann eine untergeordnete Softwareschicht ein Unterprogramm (oder eine Funktion) aufrufen, das in einer übergeordneten Schicht definiert ist." - Wikipedia
Rückruf in C mit Funktionszeiger
In C wird der Rückruf mit Hilfe von Funktionszeigern implementiert. Function Pointer - wie der Name schon sagt, ist ein Zeiger auf eine Funktion.
Zum Beispiel: int (*ptrFunc) ();
Hier ist ptrFunc ein Zeiger auf eine Funktion, die keine Argumente benötigt und eine ganze Zahl zurückgibt. Vergessen Sie nicht, die Klammern zu setzen, da der Compiler sonst davon ausgeht, dass ptrFunc ein normaler Funktionsname ist, der keine Argumente benötigt und einen Zeiger auf eine ganze Zahl zurückgibt.
Hier ist etwas Code, um den Funktionszeiger zu demonstrieren.
#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;
}
Lassen Sie uns nun versuchen, das Konzept des Rückrufs in C mit Hilfe von Funktionszeigern zu verstehen.
Das vollständige Programm besteht aus drei Dateien: callback.c, reg_callback.h und 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)();
}
Wenn wir dieses Programm ausführen, lautet die Ausgabe
Dieses Programm demonstriert die Funktion Callback innerhalb von register_callback innerhalb von my_callback zurück im Hauptprogramm
Die Funktion der höheren Schicht ruft eine Funktion der niedrigeren Schicht wie ein normaler Aufruf auf, und der Callback-Mechanismus ermöglicht es der Funktion der niedrigeren Schicht, die Funktion der höheren Schicht durch einen Zeiger auf eine Callback-Funktion aufzurufen.
Rückruf in Java mit Schnittstelle
Java kennt das Konzept des Funktionszeigers nicht Es implementiert den Callback-Mechanismus durch seinen Schnittstellenmechanismus Anstelle eines Funktionszeigers deklarieren wir hier eine Schnittstelle mit einer Methode, die aufgerufen wird, wenn der Aufrufer seine Aufgabe beendet hat
Lassen Sie es mich an einem Beispiel demonstrieren:
Die Callback-Schnittstelle
public interface Callback
{
public void notify(Result result);
}
Der Anrufer oder die Klasse der höheren Stufe
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
}
}
Der Callee oder die Funktion der unteren Schicht
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
}
}
Rückruf mit EventListener-Muster
- Posten auflisten
Dieses Muster wird verwendet, um 0 bis n Beobachter/Hörer darüber zu informieren, dass eine bestimmte Aufgabe beendet ist
- Posten auflisten
Der Unterschied zwischen dem Callback-Mechanismus und dem EventListener/Observer-Mechanismus besteht darin, dass der Callback den einzigen Aufrufer benachrichtigt, während der EventListener/Observer jeden benachrichtigen kann, der an dem Ereignis interessiert ist (die Benachrichtigung kann auch an andere Teile der Anwendung gehen, die die Aufgabe nicht ausgelöst haben)
Lassen Sie es mich anhand eines Beispiels erklären.
Die Ereignisschnittstelle
public interface Events {
public void clickEvent();
public void longClickEvent();
}
Klasse Widget
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);
}
}
Klasse Schaltfläche
public class Button extends Widget{
private String mButtonText;
public Button (){
}
public String getButtonText() {
return mButtonText;
}
public void setButtonText(String buttonText) {
this.mButtonText = buttonText;
}
}
Klasse Checkbox
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;
}
}
Aktivität Klasse
package 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();
}
}
Andere Klasse
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...");
}
}
Hauptklasse
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);
}
}
Wie Sie aus dem obigen Code ersehen können, haben wir eine Schnittstelle namens events, die grundsätzlich alle Ereignisse auflistet, die in unserer Anwendung auftreten können. Die Widget-Klasse ist die Basisklasse für alle UI-Komponenten wie Schaltflächen und Kontrollkästchen. Diese UI-Komponenten sind die Objekte, die tatsächlich die Ereignisse vom Framework-Code empfangen. Die Widget-Klasse implementiert die Ereignisschnittstelle und verfügt über zwei verschachtelte Schnittstellen, nämlich OnClickEventListener & OnLongClickEventListener
Diese beiden Schnittstellen sind für das Abhören von Ereignissen zuständig, die auf den von Widget abgeleiteten UI-Komponenten wie Button oder Checkbox auftreten können. Wenn wir also dieses Beispiel mit dem früheren Callback-Beispiel mit Java Interface vergleichen, arbeiten diese beiden Schnittstellen als Callback-Schnittstelle. Der Code auf höherer Ebene (hier die Aktivität) implementiert also diese beiden Schnittstellen. Und wann immer ein Ereignis in einem Widget auftritt, wird der Code der höheren Ebene (oder die Methode dieser Schnittstellen, die im Code der höheren Ebene implementiert ist, hier also Activity) aufgerufen.
Lassen Sie mich nun den grundlegenden Unterschied zwischen Callback- und Eventlistener-Muster diskutieren. Wie wir bereits erwähnt haben, kann der Callee bei Callback nur einen einzigen Caller benachrichtigen. Aber im Falle des EventListener-Musters kann sich jeder andere Teil oder jede Klasse der Anwendung für die Ereignisse registrieren, die auf der Schaltfläche oder dem Kontrollkästchen auftreten können. Ein Beispiel für diese Art von Klasse ist die OtherClass. Wenn Sie sich den Code der OtherClass ansehen, werden Sie feststellen, dass sie sich als Listener für das ClickEvent registriert hat, das auf der in der Activity definierten Schaltfläche auftreten kann. Das Interessante daran ist, dass neben der Activity (dem Aufrufer) auch diese OtherClass benachrichtigt wird, wenn das Click-Ereignis auf der Schaltfläche eintritt.
Ein Callback ist eine Funktion, die von einer zweiten Funktion aufgerufen wird. Diese zweite Funktion weiß nicht im Voraus, welche Funktion sie aufrufen wird. Daher wird die Identität der Callback-Funktion irgendwo gespeichert oder als Parameter an die zweite Funktion übergeben wird. Diese "Identität" kann je nach Programmiersprache die Adresse des Rückrufs oder eine andere Art von Zeiger sein, oder aber der Name der Funktion. Das Prinzip ist dasselbe: Wir speichern oder übergeben eine Information, die die Funktion eindeutig identifiziert.
Wenn der Zeitpunkt gekommen ist, kann die zweite Funktion den Callback aufrufen und je nach den aktuellen Gegebenheiten Parameter übergeben. Sie kann den Rückruf sogar aus einer Reihe von möglichen Rückrufen auswählen. Die Programmiersprache muss eine Art von Syntax bereitstellen, die es der zweiten Funktion ermöglicht, den Rückruf in Kenntnis seiner "Identität" aufzurufen.
Für diesen Mechanismus gibt es eine Vielzahl von Anwendungsmöglichkeiten. Mit Callbacks kann der Entwickler einer Funktion diese anpassen, indem er sie mit den vorgesehenen Callbacks aufrufen lässt. Zum Beispiel könnte eine Sortierfunktion einen Callback als Parameter annehmen, und dieser Callback könnte eine Funktion zum Vergleich zweier Elemente sein, um zu entscheiden, welches zuerst kommt.
Übrigens: Je nach Programmiersprache kann das Wort "Funktion" in der obigen Diskussion durch "Block", "Closure", "Lambda" usw. ersetzt werden.
Nehmen wir an, Sie geben mir eine potenziell langwierige Aufgabe: Suchen Sie die Namen der ersten fünf Personen, denen Sie begegnen. Das könnte Tage dauern, wenn ich mich in einem dünn besiedelten Gebiet befinde. Sie sind nicht wirklich daran interessiert, die Hände in den Schoß zu legen, während ich herumlaufe, also sagen Sie: "Wenn Sie die Liste haben, rufen Sie mich auf meinem Handy an und lesen Sie sie mir vor. Hier ist die Nummer.".
Sie haben mir eine Callback-Referenz gegeben - eine Funktion, die ich ausführen soll, um die weitere Verarbeitung zu übergeben.
In JavaScript könnte es etwa so aussehen:
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);
Dies könnte wahrscheinlich in vielerlei Hinsicht verbessert werden. Sie könnten z. B. einen zweiten Rückruf anbieten: Wenn es länger als eine Stunde dauert, rufen Sie das rote Telefon an und sagen Sie der Person, die abnimmt, dass die Zeit abgelaufen ist.
Um Rückrufe zu lernen, müssen Sie zuerst den Zeiger lernen. Sobald die Schüler die Idee des Zeigers auf eine Variable verstehen, wird die Idee der Rückrufe einfacher. Wenn Sie C/C++ verwenden, können Sie diese Schritte befolgen.
- Zeigen Sie Ihren Schülerinnen und Schülern zunächst, wie sie neben den normalen Variablenbezeichnern auch Variablen mit Zeigern verwenden und manipulieren können.
- Bringen Sie ihnen dann bei, dass es Dinge gibt, die nur mit Zeigern möglich sind (z. B. die Übergabe einer Variablen per Referenz).
- Erklären Sie ihnen dann, dass ausführbarer Code oder Funktionen genau wie andere Daten (oder Variablen) im Speicher sind. Funktionen haben also auch Adressen oder Zeiger.
- Zeigen Sie ihnen dann, wie Funktionen mit Funktionszeigern aufgerufen werden können, und erklären Sie ihnen, dass diese Rückrufe genannt werden.
- Nun stellt sich die Frage, warum dieser ganze Aufwand für den Aufruf einiger Funktionen? Was ist der Nutzen? Wie Datenzeiger haben auch Funktionszeiger, auch bekannt als Rückrufe, einige Vorteile gegenüber der Verwendung normaler Bezeichner.
- Der erste ist, dass Funktionsbezeichner oder Funktionsnamen nicht als normale Daten verwendet werden können. Ich meine, Sie können keine Datenstruktur mit Funktionen erstellen (wie ein Array oder eine verknüpfte Liste von Funktionen). Aber mit Rückrufen können Sie ein Array oder eine verknüpfte Liste erstellen oder sie mit anderen Daten wie einem Wörterbuch mit Schlüssel-Wert-Paaren oder Bäumen oder anderen Dingen verwenden. Dies ist ein mächtiger Vorteil. Und andere Vorteile sind eigentlich ein Kind dieses Vorteils.
- Am häufigsten werden Rückrufe in der Ereignistreiberprogrammierung verwendet. Hier werden eine oder mehrere Funktionen auf der Grundlage eines eingehenden Signals ausgeführt. Mit Callbacks kann ein Wörterbuch gepflegt werden, um Signale mit Callbacks abzubilden. Dadurch werden die Auflösung des Eingangssignals und die Ausführung des entsprechenden Codes viel einfacher.
- Die zweite Verwendung von Rückrufen, die mir in den Sinn kommt, sind Funktionen höherer Ordnung. Die Funktionen, die andere Funktionen als Eingangsargumente annehmen. Und um Funktionen als Argumente zu senden, brauchen wir Rückrufe. Ein Beispiel wäre eine Funktion, die ein Array und einen Rückruf entgegennimmt. Dann führt sie den Rückruf für jedes Element des Arrays aus und gibt die Ergebnisse in einem anderen Array zurück. Wenn wir der Funktion einen Verdopplungs-Callback übergeben, erhalten wir ein Array mit verdoppeltem Wert. Wenn wir einen Rückruf zum Quadrieren übergeben, erhalten wir Quadrate. Für Quadratwurzeln senden Sie einfach den entsprechenden Callback. Dies kann nicht mit normalen Funktionen gemacht werden.
Es könnte noch viel mehr Dinge geben. Beziehen Sie die Schüler ein und sie werden es entdecken. Ich hoffe, das hilft.
Stellen Sie sich vor, eine Freundin verlässt Ihr Haus und Sie sagen ihr: "Ruf mich an, wenn du nach Hause kommst, damit ich weiß, dass du gut angekommen bist". zurückrufen . Das ist es, was eine Callback-Funktion ist, unabhängig von der Sprache. Sie wollen, dass eine Prozedur die Kontrolle an Sie zurückgibt, wenn sie eine Aufgabe erledigt hat, also geben Sie ihr eine Funktion, mit der sie zu Ihnen zurückrufen kann.
In Python, zum Beispiel,
grabDBValue( (lambda x: passValueToGUIWindow(x) ))
grabDBValue
könnte so geschrieben werden, dass es nur einen Wert aus einer Datenbank holt und Sie dann angeben können, was mit dem Wert geschehen soll, also eine Funktion akzeptiert. Sie wissen nicht, wann oder ob grabDBValue
zurückkehren wird, aber wenn dies der Fall ist, wissen Sie, was Sie tun wollen. Hier gebe ich eine anonyme Funktion ein (oder lambda ), die den Wert an ein GUI-Fenster sendet. Auf diese Weise könnte ich das Verhalten des Programms leicht ändern:
grabDBValue( (lambda x: passToLogger(x) ))
Rückrufe funktionieren gut in Sprachen, in denen die Funktionen erste Klasse Werte, genau wie die üblichen Ganzzahlen, Zeichenketten, Booleschen Werte usw. In C können Sie eine Funktion "weitergeben", indem Sie einen Zeiger darauf übergeben, den der Aufrufer dann verwenden kann; in Java fragt der Aufrufer nach einer statischen Klasse eines bestimmten Typs mit einem bestimmten Methodennamen, da es außerhalb von Klassen keine Funktionen ("Methoden", wirklich) gibt; und in den meisten anderen dynamischen Sprachen können Sie eine Funktion mit einer einfachen Syntax einfach übergeben.
Tipp:
In Sprachen mit lexikalisches Scoping (wie Scheme oder Perl) können Sie einen Trick wie diesen anwenden:
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
ist in diesem Fall 6
weil der Rückruf Zugriff auf die Variablen hat, die in der Datei lexikalisch Umgebung, in der sie definiert wurde. Lexikalischer Geltungsbereich und anonyme Rückrufe sind eine leistungsstarke Kombination, die für Programmieranfänger eine weitere Untersuchung rechtfertigt.