Обратный вызов (компьютерное программирование)
Эта статья нуждается в дополнительных цитатах для проверки . ( сентябрь 2015 г. ) |
В компьютерном программировании обратный вызов — это функция , которая хранится в виде данных ( ссылка ) и предназначена для вызова другой функцией — часто обратно на исходный уровень абстракции .
Функция, которая принимает параметр обратного вызова, может быть спроектирована так, чтобы выполнять обратный вызов перед возвратом к вызывающей стороне, что называется синхронным или блокирующим . Функция, принимающая обратный вызов, может быть предназначена для хранения обратного вызова, чтобы его можно было вызвать обратно после возврата, что известно как асинхронный , неблокирующий или отложенный .
Языки программирования поддерживают обратные вызовы различными способами, такими как указатели на функции , лямбда-выражения и блоки .
Аналогия
[ редактировать ]Чтобы помочь понять концепцию, приведем аналогию из реальной жизни.
Покупатель приходит в магазин, чтобы сделать заказ. Это как первый звонок.
Покупатель передает продавцу список товаров, чек на их стоимость и инструкции по доставке. Это параметры первого вызова, включая обратный вызов, который представляет собой инструкции по доставке. Подразумевается, что чек будет обналичен и инструкции будут соблюдены.
Когда сотрудники могут, они доставляют товары в соответствии с инструкциями, что похоже на обратный вызов.
Примечательно, что доставку не обязательно должен осуществлять продавец, принявший заказ. Обратный вызов не обязательно должен вызываться функцией, которая приняла обратный вызов в качестве параметра.
Кроме того, доставка не обязательно осуществляется непосредственно покупателю. Обратный вызов не обязательно должен быть направлен на вызывающую функцию. Фактически, функция обычно не передает себя в качестве обратного вызова. Некоторые считают, что использование обратного вызова вводит в заблуждение, поскольку вызов (как правило) не возвращается к первоначальному абоненту, как при телефонном звонке .
Использовать
[ редактировать ]Блокирующий обратный вызов выполняется в контексте выполнения функции, передающей обратный вызов. Отложенный обратный вызов может выполняться в другом контексте, например, во время прерывания или из потока . Таким образом, отложенный обратный вызов можно использовать для синхронизации и делегирования работы другому потоку.
Обработка событий
[ редактировать ]Обратный вызов можно использовать для обработки событий. Часто потребляющий код регистрирует обратный вызов для события определенного типа. Когда происходит это событие, вызывается обратный вызов.
Обратные вызовы часто используются для программирования графического пользовательского интерфейса (GUI) программы, работающей в оконной системе . Приложение предоставляет ссылку на пользовательскую функцию обратного вызова для вызова оконной системы. Оконная система вызывает эту функцию, чтобы уведомить приложение о таких событиях, как щелчки мыши и нажатия клавиш .
Асинхронное действие
[ редактировать ]Обратный вызов может использоваться для реализации асинхронной обработки. Вызывающий объект запрашивает действие и предоставляет обратный вызов, который будет вызван после завершения действия, что может занять много времени после выполнения запроса.
Полиморфизм
[ редактировать ]Обратный вызов может использоваться для реализации полиморфизма . В следующем псевдокоде SayHi
могу взять либо WriteStatus
или WriteError
.
def WriteStatus(string message):
Write(stdout, message)
def WriteError(string message):
Write(stderr, message)
def SayHi(write):
write("Hello world")
Условное действие
[ редактировать ]Обратный вызов можно использовать для реализации условного поведения. В следующем псевдокоде, если ведение журнала включено, Log
вызывает обратный вызов, getMessage
, и записывает результат. Но если ведение журнала не включено, то getMessage
не вызывается; экономия затрат времени выполнения.
def Log(getMessage):
if isLoggingEnabled:
message = getMessage();
WriteLine(message);
Выполнение
[ редактировать ]Технология обратного вызова реализуется по-разному в разных языках программирования .
В ассемблере , C , C++ , Pascal , Modula2 и других языках функция обратного вызова хранится внутри как указатель на функцию . Использование одного и того же хранилища позволяет различным языкам напрямую совместно использовать обратные вызовы без во время разработки или выполнения взаимодействия уровня . Например, Windows API доступен через несколько языков, компиляторов и ассемблеров.
C++ также позволяет объектам обеспечивать реализацию операции вызова функции. Стандартная библиотека шаблонов принимает эти объекты (называемые функторами ) в качестве параметров.
Многие динамические языки , такие как JavaScript , Lua , Python , Perl. [1] [2] и PHP позволяют передавать объект функции.
Языки CLI , такие как C# и VB.NET, предоставляют типобезопасную ссылку на инкапсулирующую функцию, известную как делегат .
События и обработчики событий , используемые в языках .NET, обеспечивают обратные вызовы.
Функциональные языки обычно поддерживают функции первого класса , которые можно передавать в качестве обратных вызовов другим функциям, хранить в виде данных или возвращать из функций.
Многие языки, включая Perl, Python, Ruby , Smalltalk , C++ (11+), C# и VB.NET (новые версии), а также большинство функциональных языков, поддерживают лямбда-выражения , безымянные функции со встроенным синтаксисом, которые обычно действуют как обратные вызовы.
В некоторых языках, включая Scheme , ML , JavaScript, Perl, Python, Smalltalk, PHP (начиная с версии 5.3.0), [3] C++ (11+), Java (с 8), [4] и многих других, лямбда может быть замыканием , т. е. может обращаться к переменным, локально определенным в контексте, в котором определена лямбда.
В объектно-ориентированном языке программирования, таком как версии Java , перед аргументами, имеющими значение функции, поведение обратного вызова может быть достигнуто путем передачи объекта, реализующего интерфейс. Методы этого объекта являются обратными вызовами.
В PL/I и ALGOL 60 процедура обратного вызова может нуждаться в возможности доступа к локальным переменным в содержащихся блоках, поэтому она вызывается через входную переменную, содержащую как точку входа, так и контекстную информацию. [5]
Пример кода
[ редактировать ]С
[ редактировать ]Обратные вызовы имеют множество применений, например, при сигнализации об ошибках: программа Unix может не захотеть немедленно завершать работу при получении SIGTERM , поэтому, чтобы убедиться, что ее завершение обрабатывается правильно, она регистрирует функцию очистки как обратный вызов. Обратные вызовы также могут использоваться для контроля того, действует функция или нет: Xlib позволяет указывать пользовательские предикаты, чтобы определить, желает ли программа обрабатывать событие.
В следующем коде C функция PrintNumber
использует параметр getNumber
как блокирующий обратный вызов. PrintNumber
вызывается с GetAnswerToMostImportantQuestion
который действует как функция обратного вызова. При запуске выводится: «Значение: 42».
#include <stdio.h>
#include <stdlib.h>
void PrintNumber(int (*getNumber)(void)) {
int val = getNumber();
printf("Value: %d\n", val);
}
int GetAnswerToMostImportantQuestion(void) {
return 42;
}
int main(void) {
PrintNumber(GetAnswerToMostImportantQuestion);
return 0;
}
С++
[ редактировать ]В C++ функтор в дополнение к указателю на функцию можно использовать .
С#
[ редактировать ]В следующем C# коде
метод Helper.Method
использует параметр callback
как блокирующий обратный вызов. Helper.Method
вызывается с Log
который действует как функция обратного вызова. При запуске в консоль пишет следующее: «Обратный вызов: Hello world».
public class MainClass
{
static void Main(string[] args)
{
Helper helper = new Helper();
helper.Method(Log);
}
static void Log(string str)
{
Console.WriteLine($"Callback was: {str}");
}
}
public class Helper
{
public void Method(Action<string> callback)
{
callback("Hello world");
}
}
Котлин
[ редактировать ]В следующем коде Kotlin функция askAndAnswer
использует параметр getAnswer
как блокирующий обратный вызов. askAndAnswer
вызывается с getAnswerToMostImportantQuestion
который действует как функция обратного вызова. Запустив это, пользователь узнает, что ответ на его вопрос — «42».
fun main() {
print("Enter the most important question: ")
val question = readLine()
askAndAnswer(question, ::getAnswerToMostImportantQuestion)
}
fun getAnswerToMostImportantQuestion(): Int {
return 42
}
fun askAndAnswer(question: String?, getAnswer: () -> Int) {
println("Question: $question")
println("Answer: ${getAnswer()}")
}
JavaScript
[ редактировать ]В следующем коде JavaScript функция calculate
использует параметр operate
как блокирующий обратный вызов. calculate
вызывается с multiply
а потом с sum
которые действуют как функции обратного вызова.
function calculate(a, b, operate) {
return operate(a, b);
}
function multiply(a, b) {
return a * b;
}
function sum(a, b) {
return a + b;
}
// outputs 20
alert(calculate(10, 2, multiply));
// outputs 12
alert(calculate(10, 2, sum));
Метод сбора .each()
библиотеки jQuery . использует переданную ему функцию в качестве обратного вызова блокировки Он вызывает обратный вызов для каждого элемента коллекции. Например:
$("li").each(function(index) {
console.log(index + ": " + $(this).text());
});
Отложенные обратные вызовы обычно используются для обработки событий от пользователя, клиента и таймеров. Примеры можно найти в addEventListener
, Аякс и XMLHttpRequest
.
[6]
Помимо использования обратных вызовов в исходном коде JavaScript, функции C, принимающие функцию, поддерживаются через js-ctypes. [7]
Красный и РЕБОЛ
[ редактировать ]Следующий код REBOL / Red демонстрирует использование обратного вызова.
- Поскольку для оповещения требуется строка, форма создает строку из результата вычисления.
- Ключевое слово! значения (т. е. :calc-product и :calc-sum) заставляют интерпретатор возвращать код функции, а не вычислять ее с помощью функции.
- Тип данных! ссылки в блоке! [плавать! целое число!] ограничивает тип значений, передаваемых в качестве аргументов.
Red [Title: "Callback example"]
calculate: func [
num1 [number!]
num2 [number!]
callback-function [function!]
][
callback-function num1 num2
]
calc-product: func [
num1 [number!]
num2 [number!]
][
num1 * num2
]
calc-sum: func [
num1 [number!]
num2 [number!]
][
num1 + num2
]
; alerts 75, the product of 5 and 15
alert form calculate 5 15 :calc-product
; alerts 20, the sum of 5 and 15
alert form calculate 5 15 :calc-sum
Два
[ редактировать ]Пример цветовой анимации с использованием движка Roblox , который принимает необязательный обратный вызов .done:
wait(1)
local DT = wait()
function tween_color(object, finish_color, fade_time)
local step_r = finish_color.r - object.BackgroundColor3.r
local step_g = finish_color.g - object.BackgroundColor3.g
local step_b = finish_color.b - object.BackgroundColor3.b
local total_steps = 1/(DT*(1/fade_time))
local completed;
coroutine.wrap(function()
for i = 0, 1, DT*(1 / fade_time) do
object.BackgroundColor3 = Color3.new (
object.BackgroundColor3.r + (step_r/total_steps),
object.BackgroundColor3.g + (step_g/total_steps),
object.BackgroundColor3.b + (step_b/total_steps)
)
wait()
end
if completed then
completed()
end
end)()
return {
done = function(callback)
completed = callback
end
}
end
tween_color(some_object, Color3.new(1, 0, 0), 1).done(function()
print "Color tweening finished!"
end)
Питон
[ редактировать ]В следующем коде Python функция calculate
принимает параметр operate
который используется как блокирующий обратный вызов. calculate
вызывается с square
который действует как функция обратного вызова.
def square(val):
return val ** 2
def calculate(operate, val):
return operate(val)
# outputs: 25
calculate(square, 5)
Юлия
[ редактировать ]В следующем коде Джулии функция calculate
принимает параметр operate
который используется как блокирующий обратный вызов. calculate
вызывается с square
который действует как функция обратного вызова.
julia> square(val) = val^2
square (generic function with 1 method)
julia> calculate(operate,val) = operate(val)
calculate (generic function with 1 method)
julia> calculate(square,5)
25
См. также
[ редактировать ]- Шаблон команды
- Стиль продолжения прохождения
- Цикл событий
- Программирование, управляемое событиями
- Неявный вызов
- Инверсия управления
- libsigc++ — библиотека обратного вызова для C++.
- Сигналы и слоты
- Пользовательский выход
Ссылки
[ редактировать ]- ^ «Кулинарная книга Perl — 11.4. Получение ссылок на функции» . 2 июля 1999 года . Проверено 3 марта 2008 г.
- ^ «Продвинутое программирование на Perl — 4.2 Использование ссылок на подпрограммы» . 2 июля 1999 года . Проверено 3 марта 2008 г.
- ^ «Справочник по языку PHP — анонимные функции» . Проверено 8 июня 2011 г.
- ^ «Что нового в JDK 8» . oracle.com .
- ^ Белзер, Джек; Хольцман, Альберт Дж; Кент, Аллен, ред. (1979). Энциклопедия компьютерных наук и технологий: Том 12 . Марсель Деккер, Inc. п. 164. ИСБН 0-8247-2262-0 . Проверено 28 января 2024 г.
- ^ «Создание обратных вызовов JavaScript в компонентах» . Архив. Веб-документы UDN (страница документации). сек. JavaScript функционирует как обратные вызовы. Архивировано из оригинала 16 декабря 2021 г. Проверено 16 декабря 2021 г.
- ^ Холли, Бобби; Шепард, Эрик (ред.). «Объявление и использование обратных вызовов» . Документы. Сеть разработчиков Mozilla (страница документации). Архивировано из оригинала 17 января 2019 г. Проверено 16 декабря 2021 г.
Внешние ссылки
[ редактировать ]- Основные инстинкты: реализация уведомлений об обратном вызове с использованием делегатов - журнал MSDN , декабрь 2002 г.
- Реализация процедур обратного вызова на Java
- Реализация платформы обратного вызова сценариев в ASP.NET 1.x — проект кода, 2 августа 2004 г.
- Взаимодействие функций-членов C++ с библиотеками C (архивировано из оригинала 6 июля 2011 г.)
- Пример использования стиля №2: универсальные обратные вызовы