Автоматное программирование
- 1 year ago
- 0
- 0
Callback ( англ. call — вызов, англ. back — обратный) или фу́нкция обра́тного вы́зова в программировании — передача исполняемого кода в качестве одного из параметров другому коду. Обратный вызов позволяет в функции исполнять код, который задаётся в аргументах при её вызове. Этот код может быть определён в других контекстах программного кода и быть недоступным для прямого вызова из этой функции. Некоторые алгоритмические задачи в качестве своих входных данных имеют не только числа или объекты, но и действия (алгоритмы), которые естественным образом задаются как обратные вызовы.
Концепция обратного вызова имеет много приложений. Например, некоторые алгоритмы (функции) в качестве подзадачи имеют задачу вычисления хеш-значения от строки. В аргументах при запуске алгоритма (функции) удобно задавать, какую именно функцию использовать для вычисления хеш-значений.
Другой пример алгоритма, которому естественно передавать в аргументе функцию, — алгоритм обхода какого-либо хранилища объектов с применением некоторого действия к каждому объекту. Обратный вызов может выступать в роли этого действия (алгоритма).
Техника программирования обратного вызова в языках программирования, подобных
языку C
, проста. При вызове основной
функции
ей просто передаётся
указатель
на функцию обратного вызова. Классическим примером является функция
qsort
из
библиотеки stdlib
. Эта функция позволяет отсортировать массив блоков байтов одинаковой длины. В качестве аргументов она получает адрес первого элемента массива, количество блоков в массиве, размер блока байтов, и указатель на функцию сравнения двух блоков байтов. Эта функция сравнения и есть функция обратного вызова в данном примере:
#include <stdlib.h>
// функция сравнения целых чисел по модулю
int compare_abs(const void *a, const void *b) {
int a1 = *(int*)a;
int b1 = *(int*)b;
return abs(a1) - abs(b1);
}
int main() {
int size = 10;
int m[size] = {1, -3, 5, -100, 7, 33, 44, 67, -4, 0};
// сортировка массива m по возрастанию модулей
qsort(m, size, sizeof(int), compare_abs);
return 0;
}
Об обратном вызове можно думать как о действии, передаваемом некоторой основной процедуре в качестве аргумента. И это действие может рассматриваться как:
Показанный выше пример как раз соответствует первому случаю. Случай, когда обратный вызов используется как «телефонная связь», отражает код, где задаётся функция обработки определённого сигнала:
#include <stdio.h>
#include <signal.h>
volatile sig_atomic_t br = 1;
void sig(int signum)
{
br=0;
}
int main(int argc, char *argv[])
{
signal(SIGINT, sig);
printf("Press break keyboard key combination to stop the program\n");
while(br);
printf("Received SIGINT, exit\n");
return 0;
}
В некоторых языках программирования, таких как Common Lisp , Erlang , Scheme , Clojure , PHP , JavaScript , Perl , Python , Ruby и других, есть возможность конструировать анонимные (не именованные) функции и функции-замыкания прямо в выражении вызова основной функции, и эта возможность широко используется.
В технологии AJAX при выполнении асинхронного запроса к серверу необходимо указывать функцию обратного вызова, которая будет вызвана, как только придёт ответ на запрос. Часто эту функцию определяют «прямо на месте», не давая ей никакого определённого имени:
new Ajax.Request('http://example.com/do_it',
{
method: 'post',
onSuccess: function(transport) { // функция, вызываемая
window.alert("Done!"); // при успешном выполнении запроса
}, //
onFailure: function() { // функция, вызываемая
window.alert("Error!"); // при ошибке выполнения запроса
}
});
Функция обратного вызова используется также в
шаблоне проектирования
«
Наблюдатель
» (Observer). Так, например, используя библиотеку
Prototype
, можно создать «наблюдателя», который следит за нажатиями на элемент с идентификатором
"my_button"
и при получении события пишет сообщение внутрь элемента
"message_box"
:
Event.observe ($("my_button"), 'click', function() {
$("message_box").innerHTML = "Вы нажали на кнопку!"
});
Функция обратного вызова является альтернативой полиморфизму функций, а именно, позволяет создавать функции более общего назначения вместо того, чтобы создавать серию функций, одинаковых по структуре, но отличающихся лишь в отдельных местах исполняемыми подзадачами. Функции, принимающие в качестве аргументов другие функции или возвращающие функции в качестве результата, называют функциями высшего порядка . Техника обратного вызова играет важную роль для достижения повторного использования кода .
Для лучшего понимания причин использования обратного вызова рассмотрим простую задачу выполнения следующих операций над списком чисел: напечатать все числа, возвести все числа в квадрат, увеличить все числа на 1, обнулить все элементы. Ясно, что алгоритмы выполнения этих четырёх операций схожи — это цикл обхода всех элементов списка с некоторым действием в теле цикла, применяемый к каждому элементу. Это несложный код, и в принципе можно написать его 4 раза. Но давайте рассмотрим более сложный случай, когда список у нас хранится не в памяти, а на диске, и со списком могут работать несколько процессов одновременно и необходимо решать проблемы синхронизации доступа к элементам (несколько процессов могут выполнять разные задачи — удаления некоторых элементов из списка, добавления новых, изменение существующих элементов в списке). В этом случае задача обхода всех элементов списка будет довольно сложным кодом, который не хотелось бы копировать несколько раз. Правильнее создать функцию общего назначения для обхода элементов списка и дать возможность программистам абстрагироваться от того, как именно устроен алгоритм обхода и писать лишь функцию обратного вызова для обработки отдельного элемента списка.
Структурирование программного обеспечения через функции обратного вызова — очень удобный и широко используемый подход, так как при этом поведение программы с неизменным (в том числе закрытым) кодом можно изменять в очень широких пределах. Это реализуется двумя путями — или «альтернативной реализацией» какой-либо функции, или «добавлением в цепочку вызовов» ещё одной функции.
Как правило, разработчик реализует через обратные вызовы не всю функциональность программы, а лишь ту, которую предполагается расширять или изменять плагинами . Для подключения плагинов предоставляется специальная процедура, которая и заменяет «стандартные» обратные функции от разработчика на альтернативные из плагина.
Самым известным примером такого подхода является операционная система Microsoft Windows , где функции обратного вызова именуются «handler» («обработчик»), и существует возможность вставить дополнительную процедуру между любыми двумя стандартными. Этот подход называется «перехват событий» и используется, например: антивирусами для проверки файлов, к которым производится обращение; вирусами для считывания вводимых с клавиатуры символов; сетевыми фильтрами для сбора статистики и блокирования пакетов.
В современных Unix и Linux системах существует возможность динамической загрузки и выгрузки модулей ядра, работа которых также основана на функциях обратного вызова. При этом существует модуль (расширение ядра) FUSE , который, в свою очередь, предоставляет возможность обычным пользовательским программам обслуживать (перехватывать) запросы к виртуальным файловым системам.
В программном обеспечении иногда встречается декомпозиция программ, полностью основанная на функциях обратного вызова, что несколько ухудшает читаемость кода, но даёт максимальные возможности для плагинов. Пример такого продукта — DokuWiki .
Достоинства:
Недостатки: