Devolución de llamada (programación)

La versión actual de la página aún no ha sido revisada por colaboradores experimentados y puede diferir significativamente de la versión revisada el 21 de enero de 2021; las comprobaciones requieren 3 ediciones .

Devolución de llamada ( llamada en inglés   - llamada, devolución en inglés  - inversa) o función de devolución de llamada en programación  : pasar el código ejecutable como uno de los parámetros de otro código. La devolución de llamada permite que la función ejecute el código que se especifica en los argumentos cuando se llama. Este código se puede definir en otros contextos de código y no se puede llamar directamente desde esta función. Algunas tareas algorítmicas tienen como entrada no solo números u objetos, sino también acciones (algoritmos), que se especifican naturalmente como devoluciones de llamada.  

Aplicación

El concepto de devolución de llamada tiene muchas aplicaciones. Por ejemplo, algunos algoritmos (funciones) tienen como subtarea la tarea de calcular un valor hash a partir de una cadena. En los argumentos al iniciar el algoritmo (función), es conveniente especificar qué función usar para calcular los valores hash.

Otro ejemplo de un algoritmo para el que es natural pasar una función como argumento es un algoritmo para atravesar algún almacén de objetos, aplicando alguna acción a cada objeto. Una devolución de llamada puede actuar como esta acción (algoritmo).

La técnica de programación de devolución de llamada en lenguajes de programación como C es simple. Cuando se llama a la función principal, simplemente se pasa un puntero a la función de devolución de llamada. El ejemplo clásico es una función qsortde la biblioteca stdlib . Esta función ordena una matriz de bloques de bytes de la misma longitud. Como argumentos, recibe la dirección del primer elemento de la matriz, el número de bloques de la matriz, el tamaño del bloque de bytes y un puntero a una función para comparar dos bloques de bytes. Esta función de comparación es la función de devolución de llamada en este ejemplo:

#incluir <stdlib.h> // funcion para comparar enteros modulo int compare_abs ( const void * a , const void * b ) { int a1 = * ( int * ) a ; int b1 = * ( int * ) b ; volver abs ( a1 ) - abs ( b1 ); } int principal () { tamaño int = 10 ; int m [ tamaño ] = { 1 , -3 , 5 , -100 , 7 , 33 , 44 , 67 , -4 , 0 }; // ordenando la matriz m en módulos ascendentes qsort ( m , size , sizeof ( int ), compare_abs ); devolver 0 ; }

Puede pensar en una devolución de llamada como una acción que se pasa como argumento a algún procedimiento principal. Y esta acción se puede ver como:

  • subtarea y se utilizará para procesar datos dentro de este procedimiento;
  • “Conexión telefónica” se usa para “contactar” con quien llamó al procedimiento cuando ocurre algún evento ( la devolución de llamada en inglés  se traduce literalmente como “devolución de llamada”).

El ejemplo mostrado arriba corresponde exactamente al primer caso. El caso en que la devolución de llamada se usa como una "conexión telefónica" refleja el código donde se da la función para manejar una señal en particular:

#incluir <stdio.h> #incluir <señal.h> volátil sig_atomic_t br = 1 ; firma nula ( int signum ) { br = 0 ; } int principal ( int argc , char * argv []) { señal ( SIGINT , sig ); printf ( "Presione romper la combinación de teclas del teclado para detener el programa \n " ); mientras ( br ); printf ( "Recibido SIGINT, salir \n " ); devolver 0 ; }

En algunos lenguajes de programación, como Common Lisp , Erlang , Scheme , Clojure , PHP , JavaScript , Perl , Python , Ruby y otros, es posible construir funciones anónimas (sin nombre) y funciones de cierre directamente en la expresión de llamada de función principal, y esta oportunidad es ampliamente utilizada.

En la tecnología AJAX , al realizar una solicitud asíncrona al servidor, debe especificar una función de devolución de llamada que se llamará tan pronto como llegue la respuesta a la solicitud. A menudo, esta función se define "en su lugar" sin darle ningún nombre específico:

nuevo Ajax . Request ( 'http://example.com/do_it' , { method : 'post' , onSuccess : function ( transport ) { // función llamada por window . alert ( "¡Listo!" ); // si la solicitud fue exitosa }, // onFailure : function () { // función llamada por ventana . alerta ( "¡Error!" ); // en caso de error de ejecución de la solicitud } });

La función de devolución de llamada también se usa en el patrón de diseño Observer . Entonces, por ejemplo, utilizando la biblioteca Prototype , puede crear un "observador" que monitorea los clics en un elemento con un identificador "my_button"y, cuando se recibe un evento, escribe un mensaje dentro del elemento "message_box":

evento _ observe ( $ ( "my_button" ), 'click' , function () { $ ( "message_box" ). innerHTML = "¡Hiciste clic en el botón!" });

La función de devolución de llamada es una alternativa al polimorfismo de función , es decir, le permite crear funciones de un propósito más general, en lugar de crear una serie de funciones que tienen la misma estructura, pero difieren solo en ciertos lugares en las subtareas ejecutables. Las funciones que toman otras funciones como argumentos o devuelven funciones como resultado se denominan funciones de orden superior . La técnica de devolución de llamada juega un papel importante para lograr la reutilización del código .

Por qué usar devoluciones de llamada

Para comprender mejor las razones para usar una devolución de llamada, considere la tarea simple de realizar las siguientes operaciones en una lista de números: imprimir todos los números, elevar todos los números al cuadrado, aumentar todos los números en 1, establecer todos los elementos en cero. Está claro que los algoritmos para realizar estas cuatro operaciones son similares: este es un ciclo que pasa por alto todos los elementos de la lista con alguna acción en el cuerpo del ciclo, aplicada a cada elemento. Este es un código simple y, en principio, puede escribirlo 4 veces. Pero consideremos un caso más complicado, cuando la lista no está almacenada en la memoria, sino en el disco, y varios procesos pueden trabajar con la lista al mismo tiempo y es necesario resolver los problemas de sincronización del acceso a los elementos (varios procesos pueden realizar diferentes tareas: eliminar algunos elementos de la lista, agregar otros nuevos, cambiar elementos existentes en la lista). En este caso, la tarea de recorrer todos los elementos de la lista será un código bastante complejo que no le gustaría copiar varias veces. Sería más correcto crear una función de propósito general para recorrer los elementos de la lista y permitir a los programadores abstraerse de cómo funciona el algoritmo de recorrido y escribir solo una función de devolución de llamada para procesar un solo elemento de la lista.

Estructuración de software

La estructuración del software a través de funciones de devolución de llamada es un enfoque muy conveniente y ampliamente utilizado, ya que el comportamiento de un programa con código sin cambios (incluido el cerrado) se puede cambiar en un rango muy amplio. Esto se implementa de dos maneras: ya sea mediante una "implementación alternativa" de una función o "agregando otra función a la cadena de llamadas".

Como regla general, el desarrollador no implementa toda la funcionalidad del programa a través de devoluciones de llamada, sino solo lo que se supone que debe ser ampliado o modificado por complementos . Para conectar complementos, se proporciona un procedimiento especial, que reemplaza las funciones inversas "estándar" del desarrollador con otras alternativas del complemento.

El ejemplo más famoso de este enfoque es el sistema operativo Microsoft Windows , donde las funciones de devolución de llamada se denominan "controlador" ("controlador"), y es posible insertar un procedimiento adicional entre dos estándar cualquiera. Este enfoque se denomina "intercepción de eventos" y lo utilizan, por ejemplo: los antivirus para comprobar los archivos a los que se accede; virus para leer caracteres ingresados ​​desde el teclado; filtros de red para recopilar estadísticas y bloquear paquetes.

En los sistemas Unix y Linux modernos, es posible cargar y descargar dinámicamente módulos del kernel, que también se basan en funciones de devolución de llamada. Al mismo tiempo, hay un módulo (extensión del kernel) FUSE , que, a su vez, brinda la capacidad para que los programas de usuario ordinarios sirvan (intercepten) solicitudes a sistemas de archivos virtuales.

En el software, a veces hay una descomposición de los programas que se basa completamente en las funciones de devolución de llamada, lo que empeora ligeramente la legibilidad del código, pero brinda las máximas oportunidades para los complementos. Un ejemplo de tal producto es DokuWiki .

Ventajas y desventajas

ventajas:

  • Posibilidad de cambio dinámico de funcionalidad (conexión y desconexión de plug-ins/módulos mientras se ejecuta el programa).
  • La posibilidad de un número ilimitado de variantes de la función llamada sin cambiar el código básico (en este contexto).
  • La capacidad de insertar una función llamada no solo para un comportamiento alternativo, sino también como otra subrutina (intermedia), generalmente para rastrear operaciones o cambiar parámetros para la siguiente función (llamada). Puede haber cualquier número de estos "enlaces adicionales" independientes en la cadena de llamadas.
  • Compatibilidad con funciones de devolución de llamada en la mayoría de los lenguajes de programación modernos de propósito general.

Defectos:

  • La penalización de rendimiento asociada con llamadas de "función inversa" adicionales es directamente proporcional al "costo de llamada de función" en tiempo de ejecución y el número de llamadas adicionales mientras se ejecuta el programa.
  • Deterioro en la legibilidad del código fuente: para comprender el algoritmo del programa, es necesario rastrear toda la cadena de llamadas.

Véase también

Enlaces