8 de julio de 2009

Callback en Java

Un Callback es una referencia a una acción ejecutable (no la acción en sí) que usualmente es pasada como un argumento a otra función. Es como si nos pasan un control remoto y nos indican "esto es para encender las luces"; al presionarlo podría no ser así, ya que sólo tenemos una referencia de la función. La parte de código que hace el llamado a la acción callback, mediante esa referencia (que por ejemplo le fue pasada como argumento), lo ejecuta "confiando" en quien le pasó el argumento.

Ejemplo de un Mozo

Supongamos Clientes de un Restaurante internacional, que le hacen pedidos a un mozo, en diferentes idiomas. Necesitamos que estos pedidos lleguen traducidos al cocinero.
El mozo hace estos encargos al cocinero, siempre en español.

 [Cliente A]-----\  
                  |  
 [Cliente B]-->[Mozo]--->[Cocinero]  
                  |  
 [Cliente C]-----/  

Volviendo al control remoto, supongamos que se le diera a los clientes un control remoto con un botón (una referencia), y ellos lo presionan cuando quieren por ejemplo café, pero como fuimos nosotros quienes le dimos el teclado, el efecto podría ser cualquier cosa, ellos sólo deciden cuando presionarlo, pero es el restaurante quien controla qué acción hay detrás.

Esa es la tarea del mozo, es un medio indirecto que tienen los clientes para "hacer el pedido al cocinero", lo hacen disparando una acción a través del mozo, confian que el mozo sabrá qué hacer (tal como sería confiar en el botón) por lo tanto cuando los clientes hacen el pedido (en cualquier idioma) en realidad se ejecuta una acción equivalente de hacer el pedido al cocinero (pero en el único idioma que entiende).
En este ejemplo, una forma de hacerlo en Java.

Se necesita
  • Una interface para definir el formato del callback (Caracteristicas del control remoto, Mozo Multilingue)
  • Clases que aceptan como argumento el callback (Clientes que confían en el Restaurante)
  • Clase Mozo que implementa la Interfaz (Define qué hacer cada vez que se llama al callback)

  • Interfaz
    public interface CamareroMultilingue{
        public void pedirCafe();
        // Pueden ser muchos más métodos...
    }
    
  • Un cliente que habla español
    public class ClaseEsp{
     //interfaz que permite que el pedido sea interpretado afuera de esta clase
     private CamareroMultilingue pedido; 
     private boolean hambre; //variable ejemplo
    
        // constructor
        public ClaseEsp(CamareroMultilingue p){
            pedido = p;
        }
    
        public void procesa(){
    
            hambre = true;
    
            if (hambre){
               System.out.println("Buen día, un café por favor");
               pedido.pedirCafe();
            }
        }
    }
  • Un cliente que habla francés
    public class ClaseFra{
     //interfaz que permite que el pedido sea interpretado afuera de esta clase
     private CamareroMultilingue pedido; 
     private boolean hambre; //variable ejemplo
    
       // constructor
        public ClaseFra(CamareroMultilingue p){
            pedido = p;
        }
    
        public void procesa(){
    
            hambre = true;
    
            if (hambre){
               System.out.println("Bonjour, un café s'il vous plaît?");
               pedido.pedirCafe();
            }
        }
    }
  • Un cliente que habla inglés
    public class ClaseIng{
     //interfaz que permite que el pedido sea interpretado afuera de esta clase
     private CamareroMultilingue pedido; 
     private boolean hambre; //variable ejemplo
       
        // constructor
        public ClaseIng(CamareroMultilingue p){
            pedido = p;
        }
    
        public void procesa(){
    
            hambre = true;
    
            if (hambre){
               System.out.println("Good morning, can I have a coffee please ?");
               pedido.pedirCafe();
            }
        }
    }
  • Un mozo
    public class Restaurant{
        public static void main(String[] args){
            class Mozo implements CamareroMultilingue{
               public void pedirCafe(){    
                   System.out.println("El mozo le pide al cocinero: UN JAVA!");
                   System.out.println(" ");
               }
            }
    
        //Notar que la interfaz es publica porque deben conocerla
        //todas las clases que la contienen como propiedad
        //pero! las clases que la implementan pueden ser privadas, internas, etc
        //cada una que implemente puede tener el alcance y la implementacion que quiera
        //siempre que respete los métodos y campos mínimos que propone la interfaz.
     
        Mozo mozoInterprete = new Mozo();
    
        //Clientes, al ingresar al bar se le asigna un mozo interprete (en este caso el mismo)
        ClaseEsp  ce = new ClaseEsp(mozoInterprete);
        ClaseFra  cf = new ClaseFra(mozoInterprete);
        ClaseIng  ci = new ClaseIng(mozoInterprete);
    
        //acción! se procesa el pedido de los clientes
        ce.procesa();
        cf.procesa();
        ci.procesa();
        }
    }
    Salida
    Buen día, un café por favor
    El mozo le pide al cocinero: UN JAVA!
    
    Good morning, can I have a coffee please ?
    El mozo le pide al cocinero: UN JAVA!
    
    Bonjour, un café s'il vous plaît?
    El mozo le pide al cocinero: UN JAVA!
¿Hay otra forma de hacerlo? Sí, otra forma es utilizando una clase abstracta, esta clase implementa algunos métodos pero otros los podemos dejar "vacíos" para que alguna clase la extienda y los pueda implementar, les defina la funcionalidad. Evidentemente esta es otra manera de hacer callback, ya que la clase abstracta puede llamar a los métodos cada vez que los necesita (de acuerdo a alguna función esperada), y el contenido de esos métodos (qué es lo que realmente hacen) se define afuera, en la clase que la extiende.