Как объяснить обратные вызовы на простом английском языке? Чем они отличаются от вызова одной функции из другой, принимая некоторый контекст из вызывающей функции? Как объяснить их силу начинающему программисту?
Как объяснить обратные вызовы на простом английском языке? Чем они отличаются от вызова одной функции из другой, принимая некоторый контекст из вызывающей функции? Как объяснить их силу начинающему программисту?
Ответы:
Часто приложение должно выполнять различные функции в зависимости от его контекста / состояния. Для этого мы используем переменную, в которой мы будем хранить информацию о вызываемой функции. В соответствии со своими потребностями приложение установит эту переменную с информацией о вызываемой функции и вызовет функцию, используя ту же самую переменную.
В 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
может привести к внутреннему блокированию в ожидании, когда ОС выполнит свою черную магию, после которой выполняется обратный вызов. В таком случае нет разницы в результате.
Как объяснить обратные вызовы на простом английском языке?
Говоря простым языком, функция обратного вызова похожа на Работника, который «перезванивает» своему Менеджеру после завершения Задачи .
Чем они отличаются от вызова одной функции из другой, принимая некоторый контекст из вызывающей функции?
Это правда, что вы вызываете функцию из другой функции, но суть в том, что обратный вызов обрабатывается как объект, так что вы можете изменить какую функцию вызывать в зависимости от состояния системы (например, шаблон проектирования стратегии).
Как объяснить их силу начинающему программисту?
Мощь обратных вызовов можно легко увидеть на веб-сайтах в стиле 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
, который напоминает работнику, которому необходимо получить информацию от менеджера, прежде чем он сможет выполнить свою задачу . Чтобы создать 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
от Дугласа Crockford :
Функции могут быть определены внутри других функций. Внутренняя функция имеет доступ к переменным и параметрам внешней функции. Если ссылка на внутреннюю функцию сохраняется (например, в качестве функции обратного вызова), переменные внешней функции также сохраняются.
Смотрите также:
Я ошеломлен, увидев, что так много умных людей не в состоянии подчеркнуть реальность, что слово «обратный вызов» стало использоваться двумя противоречивыми способами.
Оба способа включают настройку функции путем передачи дополнительной функции (определение функции, анонимное или именованное) существующей функции. то есть.
customizableFunc(customFunctionality)
Если пользовательский функционал просто подключен к блоку кода, вы настроили функцию, например, так.
customizableFucn(customFunctionality) {
var data = doSomthing();
customFunctionality(data);
...
}
Хотя этот вид внедренной функциональности часто называют «обратным вызовом», в этом нет ничего случайного. Очень очевидным примером является метод forEach, в котором пользовательская функция предоставляется в качестве аргумента для применения к каждому элементу в массиве для изменения массива.
Но это принципиально отличается от использования функций «обратного вызова» для асинхронного программирования , как в AJAX или node.js, или просто при назначении функциональности событиям взаимодействия с пользователем (например, щелчкам мыши). В этом случае вся идея состоит в том, чтобы дождаться наступления условного события, прежде чем выполнять пользовательские функции. Это очевидно в случае взаимодействия с пользователем, но также важно в процессах ввода / вывода (ввода / вывода), которые могут занимать время, например, чтение файлов с диска. Именно здесь термин «обратный вызов» имеет наиболее очевидный смысл. После запуска процесса ввода-вывода (например, запрос на чтение файла с диска или на сервер для возврата данных из http-запроса) асинхронныйПрограмма не дожидается окончания. Он может выполнять любые задачи, запланированные далее, и отвечать пользовательскими функциями только после того, как он получит уведомление о том, что прочитанный файл или запрос http завершен (или что он не выполнен) и что данные доступны для пользовательской функции. Это все равно, что позвонить в бизнес по телефону и оставить свой номер «обратного вызова», чтобы они могли звонить вам, когда кто-то готов ответить вам. Это лучше, чем повесить трубку для тех, кто знает, как долго и не может заниматься другими делами.
Асинхронное использование по своей природе включает в себя некоторые средства прослушивания желаемого события (например, завершение процесса ввода-вывода), так что, когда это происходит (и только когда это происходит), выполняется пользовательская функция «обратного вызова». В очевидном примере AJAX, когда данные фактически поступают с сервера, запускается функция «обратного вызова», которая использует эти данные для изменения DOM и, следовательно, перерисовывает окно браузера до такой степени.
Напомним. Некоторые люди используют слово «обратный вызов» для обозначения любого вида пользовательских функций, которые могут быть введены в существующую функцию в качестве аргумента. Но, по крайней мере для меня, наиболее подходящее использование слова - это когда внедренная функция «обратного вызова» используется асинхронно - для выполнения только при наступлении события, о котором она ожидает уведомления.
Array.prototype.forEach()
и функцией, переданной как аргумент setTimeout()
, и они являются лошадьми разных цветов в той степени, в какой вы рассуждаете о своей программе. ,
С точки зрения непрограммиста, обратный вызов - это заполнение пробела в программе.
На многих бумажных бланках часто встречается пункт «Человек, которого нужно вызвать в случае чрезвычайной ситуации». Там есть пустая строка. Вы пишете на чье-то имя и номер телефона. Если возникает чрезвычайная ситуация, то этого человека вызывают.
Это ключ. Вы не меняете форму (код, как правило, чужой). Однако вы можете заполнить недостающие фрагменты информации ( ваш номер).
Пример 1:
Обратные вызовы используются как настраиваемые методы, возможно, для добавления / изменения поведения программы. Например, возьмем некоторый код C, который выполняет функцию, но не знает, как напечатать вывод. Все, что он может сделать, это сделать строку. Когда он пытается выяснить, что делать со строкой, он видит пустую строку. Но программист дал вам бланк для записи вашего обратного вызова!
В этом примере вы не используете карандаш для заполнения бланка на листе бумаги, вы используете функцию set_print_callback(the_callback)
.
set_print_callback
это карандаш,the_callback
ваша информация, которую вы заполняете.Вы заполнили эту пустую строку в программе. Всякий раз, когда ему нужно напечатать вывод, он будет смотреть на эту пустую строку и следовать там инструкциям (то есть вызывать функцию, которую вы там поместили). На практике это дает возможность печати на экран, в файл журнала, на принтер, через сетевое соединение или любую их комбинацию. Вы заполнили бланк тем, что хотите сделать.
Пример 2:
Когда вам говорят, что вам нужно позвонить по номеру экстренной помощи, вы идете и читаете то, что написано на бумажном бланке, а затем набираете номер, который вы прочитали. Если эта строка пуста, ничего не будет сделано.
Программирование на Gui работает практически так же. При нажатии кнопки программа должна выяснить, что делать дальше. Идет и ищет обратный звонок. Этот обратный вызов находится в пустом месте с надписью «Вот что вы делаете, когда нажата кнопка1»
Большинство IDE автоматически заполнят для вас бланк (напишите основной метод), когда вы об этом попросите (например button1_clicked
). Однако этот бланк может иметь любой метод, который вы чертовски хорошо, пожалуйста . Вы можете вызвать метод run_computations
или butter_the_biscuits
до тех пор, пока вы поместите имя этого обратного вызова в надлежащее поле. Вы можете поставить "555-555-1212" на номер службы экстренной помощи пустым. Это не имеет особого смысла, но это допустимо.
Последнее замечание: эта пустая строка, которую вы заполняете обратным вызовом? Это может быть стерто и переписано по желанию. (стоит вам или нет - это другой вопрос, но это часть их власти)
Всегда лучше начать с примера :).
Предположим, у вас есть два модуля A и B.
Вы хотите, чтобы модуль A был уведомлен, когда какое-то событие / условие происходит в модуле B. Однако модуль B не имеет представления о вашем модуле A. Все, что он знает, - это адрес определенной функции (модуля A) через указатель на функцию, который предоставленный ему модулем А.
Таким образом, все, что B теперь должен сделать, это «обратный вызов» в модуль A, когда происходит конкретное событие / условие с использованием указателя функции. А может выполнять дальнейшую обработку внутри функции обратного вызова.
*) Очевидным преимуществом здесь является то, что вы абстрагируете все о модуле A от модуля B. Модуль B не должен заботиться о том, кто / что является модулем A.
Представьте, что вам нужна функция, которая возвращает 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
вернется, но если / когда это произойдет, вы знаете, что вы хотите, чтобы он сделал. Здесь я передаю анонимную функцию (или лямбду ), которая отправляет значение в окно графического интерфейса. Я мог легко изменить поведение программы, выполнив это:
grabDBValue( (lambda x: passToLogger(x) ))
Обратные вызовы хорошо работают в языках, где функции являются значениями первого класса , как и обычные целые числа, строки символов, логические значения и т. Д. В 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
$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);
Вероятно, это можно улучшить многими способами. Например, вы могли бы предоставить второй обратный вызов: если это займет больше часа, позвоните по красному телефону и скажите человеку, который отвечает, что у вас истекло время ожидания.
Обратные вызовы легче всего описать в терминах телефонной системы. Функциональный вызов аналогичен вызову кого-либо по телефону, задает ей вопрос, получает ответ и вешает трубку; добавление обратного вызова изменяет аналогию, так что после того, как вы зададите ей вопрос, вы также дадите ей свое имя и номер, чтобы она могла перезвонить вам с ответом. - Пол Якубик, "Реализации обратного вызова в C ++"
Обратный вызов - это функция, которая будет вызываться второй функцией. Эта вторая функция не знает заранее, какую функцию она будет вызывать. Таким образом, идентификатор функции обратного вызова хранится где-то или передается второй функции в качестве параметра. Эта «идентичность», в зависимости от языка программирования, может быть адресом обратного вызова или каким-либо другим видом указателя, или это может быть имя функции. Принцип один и тот же, мы храним или передаем некоторую информацию, которая однозначно идентифицирует функцию.
Когда придет время, вторая функция может вызвать обратный вызов, предоставив параметры в зависимости от обстоятельств на данный момент. Он может даже выбрать обратный вызов из набора возможных обратных вызовов. Язык программирования должен обеспечивать некоторый синтаксис, чтобы вторая функция могла вызывать обратный вызов, зная свою «идентичность».
Этот механизм имеет множество возможных применений. С помощью обратных вызовов, конструктор функции может позволить ее настраивать, вызывая любые предоставляемые обратные вызовы. Например, функция сортировки может принимать обратный вызов в качестве параметра, и этот обратный вызов может быть функцией для сравнения двух элементов, чтобы определить, какой из них будет первым.
Кстати, в зависимости от языка программирования слово «функция» в приведенном выше обсуждении может быть заменено на «блок», «закрытие», «лямбда» и т. Д.
Обычно мы отправляем переменные в функции. Предположим, у вас есть задача, где переменная должна быть обработана, прежде чем она будет передана в качестве аргумента - вы можете использовать обратный вызов.
function1(var1, var2)
это обычный способ.
Что если я хочу, var2
чтобы меня обработали, а затем отправили в качестве аргумента?
function1(var1, function2(var2))
Это один тип обратного вызова - где function2
выполняется некоторый код и возвращается переменная в исходную функцию.
Метафорическое объяснение:
У меня есть посылка, которую я хочу доставить другу, и я также хочу знать, когда мой друг получит ее.
Поэтому я доставляю посылку на почту и прошу их доставить. Если я хочу узнать, когда мой друг получит посылку, у меня есть два варианта:
(а) Я могу подождать в почтовом отделении, пока оно не будет доставлено.
(б) Я получу электронное письмо, когда оно будет доставлено.
Вариант (б) аналогичен обратному вызову.
Для обучения обратных вызовов, вы должны сначала научить указатель. Как только студенты поймут идею указателя на переменную, идея обратных вызовов станет легче. Предполагая, что вы используете C / C ++, эти шаги можно выполнить.
Там может быть еще много вещей. Вовлеките студентов, и они обнаружат. Надеюсь это поможет.
На простом английском языке обратный вызов - это обещание. Джо, Джейн, Дэвид и Саманта едут в машине. Джо сегодня за рулем. У Джейн, Дэвида и Саманты есть несколько вариантов:
Вариант 1: Это больше похоже на пример опроса, где Джейн застрянет в «цикле», проверяя, находится ли Джо снаружи. Джейн больше ничего не может сделать.
Вариант 2: это пример обратного вызова. Джейн говорит Джо позвонить в дверь, когда он на улице. Она дает ему «функцию», чтобы позвонить в дверь. Джо не нужно знать, как работает дверной звонок или где он находится, ему просто нужно вызвать эту функцию, т.е. позвонить в дверной звонок, когда он там.
Обратные вызовы управляются «событиями». В этом примере «событие» - это прибытие Джо. Например, в Ajax событиями могут быть «успех» или «отказ» асинхронного запроса, и каждый из них может иметь одинаковые или разные обратные вызовы.
С точки зрения приложений JavaScript и обратных вызовов. Нам также необходимо понимать «замыкания» и контекст приложения. То, к чему относится «это», может легко запутать разработчиков JavaScript. В этом примере в методе / обратном вызове каждого человека «ring_the_door_bell ()» могут быть некоторые другие методы, которые каждый человек должен сделать на основе своей утренней рутины ex. "выключить телевизор()". Мы бы хотели, чтобы «this» ссылалось на объект «Jane» или «David», чтобы каждый мог настроить все, что им нужно сделать, прежде чем Джо поднимет их. Это где настройка обратного вызова с Джо требует пародии метода, так что «это» относится к нужному объекту.
Надеюсь, это поможет!
Я думаю, что это довольно легко объяснить.
Сначала обратный вызов - это просто обычные функции.
Кроме того, мы вызываем эту функцию (назовем ее A) из другой функции (назовем ее B).
Волшебство в этом заключается в том, что я решаю, какая функция должна вызываться функцией извне B.
В то время, когда я пишу функцию BI, я не знаю, какую функцию обратного вызова следует вызвать. В то время, когда я вызываю функцию BI, я также говорю этой функции вызывать функцию A. Это все.
Что такое функция обратного вызова?
Простой ответ на этот первый вопрос заключается в том, что функция обратного вызова - это функция, которая вызывается через указатель на функцию. Если вы передаете указатель (адрес) функции в качестве аргумента другому, когда этот указатель используется для вызова функции, на которую он указывает, говорят, что обратный вызов сделан.
Функция обратного вызова сложно отследить, но иногда она очень полезна. Особенно, когда вы проектируете библиотеки. Функция обратного вызова аналогична просьбе вашего пользователя дать вам имя функции, и вы вызовете эту функцию при определенных условиях.
Например, вы пишете таймер обратного вызова. Это позволяет вам указать продолжительность и функцию для вызова, и функция будет соответственно вызываться. «Запускать myfunction () каждые 10 секунд 5 раз»
Или вы можете создать каталог функций, передав список имен функций и попросив библиотеку сделать обратный вызов соответственно. «Обратный вызов success () в случае успеха, обратный вызов fail () в случае неудачи».
Давайте посмотрим на простой пример указателя на функцию
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;
}
Обратный вызов - это метод, который планируется выполнить при выполнении условия.
Примером «реального мира» является местный магазин видеоигр. Вы ждете Half-Life 3. Вместо того, чтобы каждый день ходить в магазин, чтобы посмотреть, нет ли в нем, вы регистрируете свою электронную почту в списке, чтобы получать уведомления о появлении игры. Электронная почта становится вашим «обратным вызовом», а условие, которое необходимо выполнить, - это доступность игры.
Пример «программистов» - это веб-страница, на которой вы хотите выполнить действие при нажатии кнопки. Вы регистрируете метод обратного вызова для кнопки и продолжаете выполнять другие задачи. Когда / если пользователь нажимает на кнопку, браузер просматривает список обратных вызовов для этого события и вызывает ваш метод.
Обратный вызов - это способ асинхронной обработки событий. Вы никогда не можете знать, когда будет выполнен обратный вызов, или если он будет выполнен вообще. Преимущество состоит в том, что он освобождает ваши программы и циклы ЦП для выполнения других задач в ожидании ответа.
Просто и понятно: обратный вызов - это функция, которую вы даете другой функции, чтобы она могла вызывать ее.
Обычно он вызывается, когда какая-то операция завершена. Так как вы создаете обратный вызов перед тем, как передать его другой функции, вы можете инициализировать его контекстной информацией с сайта вызова. Вот почему он называется call * back * - первая функция вызывает обратно в контекст, откуда она была вызвана.
«В компьютерном программировании обратный вызов - это ссылка на исполняемый код или фрагмент исполняемого кода, который передается в качестве аргумента другому коду. Это позволяет программному уровню более низкого уровня вызывать подпрограмму (или функцию), определенную в более высоком уровне ». - Википедия
Обратный вызов в 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;
}
Теперь давайте попробуем понять концепцию Callback в 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
}
}
Функция Callee или нижнего уровня
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);
}
}
Как видно из приведенного выше кода, у нас есть интерфейс с названием events, который в основном перечисляет все события, которые могут произойти для нашего приложения. Класс Widget является базовым классом для всех компонентов пользовательского интерфейса, таких как Button, Checkbox. Эти компоненты пользовательского интерфейса являются объектами, которые фактически получают события из кода платформы. Класс Widget реализует интерфейс Events, а также имеет два вложенных интерфейса: OnClickEventListener и OnLongClickEventListener.
Эти два интерфейса отвечают за прослушивание событий, которые могут произойти в компонентах пользовательского интерфейса, производных от виджетов, таких как Button или Checkbox. Поэтому, если мы сравним этот пример с более ранним примером Callback с использованием интерфейса Java, эти два интерфейса будут работать как интерфейс Callback. Таким образом, код более высокого уровня (Здесь Activity) реализует эти два интерфейса. И всякий раз, когда событие происходит с виджетом, будет вызываться код более высокого уровня (или метод этих интерфейсов, реализованный в коде более высокого уровня, который здесь называется Activity).
Теперь позвольте мне обсудить основные различия между шаблонами Callback и Eventlistener. Как мы уже упоминали, используя Callback, Callee может уведомить только одного абонента. Но в случае шаблона EventListener любая другая часть или класс приложения может регистрироваться для событий, которые могут происходить на кнопке или флажке. Примером такого класса является класс OtherClass. Если вы увидите код OtherClass, вы обнаружите, что он зарегистрировался в качестве прослушивателя ClickEvent, что может происходить в кнопке, определенной в Activity. Интересно, что помимо Activity (Вызывающий объект), этот 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
[отредактировано], когда у нас есть две функции, скажем, functionA и functionB , если functionA зависит от functionB .
Затем мы вызываем функцию B как функцию обратного вызова . Это широко используется в среде Spring.
Думайте о методе как о предоставлении задания коллеге. Простая задача может быть следующей:
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 . Если вы пишете свою программу синхронно, функция, которую вы пишете для загрузки данных, будет работать непрерывно, пока все данные не будут загружены. Это означает, что ваш пользовательский интерфейс не будет обновляться и в основном будет выглядеть замороженным. Если вы пишете свою программу, используя обратные вызовы, вы запрашиваете данные и говорите «выполните эту функцию, когда закончите». Это позволяет интерфейсу пользователя по-прежнему разрешать взаимодействие с пользователем во время загрузки файла. После завершения загрузки веб-страницы вызывается ваша функция результата (обратный вызов), и вы можете обрабатывать данные.
По сути, это позволяет вам запросить что-то и продолжить выполнение в ожидании результата. Как только результат возвращается к вам через функцию обратного вызова, вы можете выбрать операцию, на которой он остановился.