Eventos y Binding

Eventos

Un evento puede ser cualquier suceso en el sistema. Los eventos son una forma de comunicación, algún componente produce o dispara un evento y otros componentes lo escuchan.

La ventaja de los eventos como mecanismo de comunicación es que invierten la relación de conocimiento entre el emisor y el receptor de la comunicación. Para entenderlo lo podemos comparar con el mecanismo de comunicación que utilizamos comúnmente al modelar con objetos: el mensaje. Para poder enviar un mensaje, el emisor debe conocer al receptor. En cambio, con eventos, al emisor que produce los eventos no le interesan los posibles destinatarios, se limita a disparar un evento; y son los destinatarios tienen la responsabilidad de registrarse para recibir los eventos que les interesan. Ahí se ve la relación de conocimiento invertida: el destinatario conoce al emisor, y no al revés. Esto permite construir estrategias de comunicación entre componentes con muy bajo acoplamiento.

En cuanto a la implementación de este mecanismo, puede haber muchas variantes; muchas veces basadas en el patrón Observer (que deberían asegurarse de tener claro a esta altura). En general se necesita de algún componente que implemente el mecanismo de registración y que se ocupe de hacer llegar los eventos a todos los destinatarios.

Tanto la vista como el modelo pueden producir eventos. La vista produce eventos cuando el usuario interactúa con ella, por ejemplo cuando presiona un botón o cuando ingresa un valor en un campo de texto. Un modelo podría producir eventos cada vez que cambia.

La responsabilidad del controller en una arquitectura basada en eventos será escuchar los eventos de vista o modelo y actuar sobre el otro en consecuencia. En su versión más simple, relaciona un evento de un lado con una acción del otro; por ejemplo: "cuando el usuario presione este botón, mandale este mensaje a este objeto". Se puede ver que en este último ejemplo el controller no tiene practicamente comportamiento, se limita a relacionar eventos con acciones: Todo el comportamiento de la aplicación está en el modelo, toda la interacción visual está en la vista, el controller se limita a pasar mensajes entre los otros dos.

Binding

Como dijimos, los eventos permiten que el controlador dispare acciones sobre el modelo cuando el usuario acciona sobre la vista (o en el sentido inverso). Una forma muy común de interactuar entre modelo y vista es que el usuario ingrese un valor en algún control (por ejemplo tipeando en un campo de texto o seleccionando un elemento en un selector o combo) y nosotros querramos guardar ese valor en alguna variable en el modelo.

Eso se puede hacer perfectamente con eventos, se debe escuchar el evento que produce (por ejemplo) el campo de texto cuando el usuario tipea y asociar al evento una acción que setee en alguna variable del modelo el contenido del campo de texto. Este comportamiento es tan habitual, que muchos frameworks proveen un mecanismo específico para hacerlo, que se denomina binding(vinculación en inglés).

El binding relaciona dos propiedades entre sí haciendo que se mantengan sincronizadas: si una cambia la otra también. Con frecuencia se eligen una propiedad del modelo con una propiedad de la vista, pero no es indispensable. En nuestros primeros ejemplos la propiedad de la vista será el valor ingresado por el usuario en un control, más tarde incorporaremos otras propiedades.

El binding puede ser unidireccional o bidireccional. En caso de un binding bidireccional, cualquiera de los dos extremos del vínculo puede ser originador de las modificaciones. Esta característica será de gran utilidad porque provee una forma muy sencilla que la vista refleje automáticamente cambios en el modelo.

Adicionalmente, en muchos frameworks los bindings son capaces de hacer transformaciones. En estos casos podemos, por ejemplo vincular el valor de un campo de texto en la vista (un String) con una propiedad numérica en el modelo, y el framework se ocupa de tranformar de string a número por nosotros, y viceversa. Las tecnologías de presentación más avanzadas son capaces de realizar algunas conversiones sencillas en forma automática, y también proveen al usuario de la posibilidad de definir sus propias transformaciones.

La existencia de transformaciones asociadas al binding nos obliga a pensar en la posibilidad de que esas transformaciones fallen (por ejemplo si el valor ingresado no se puede transformar a un valor numérico), entonces el binding tiene que tener alguna posibilidad de informar errores. Así como podemos asociar transformaciones al binding, en algunas tecnologías también es posible asociarle validaciones. Pero por ahora no nos vamos a meter mucho en eso, el manejo de errores lo vamos a ver en la próxima clase.

¿Qué pasa cuando no tengo binding?

Si bien la idea de binding no es nueva, existen aún hoy muchas tecnologías que se usan y que no contemplan ese concepto. Sin embargo esas tecnologías complejizan los controllers, obligando a grandes cantidades de código burocrático. Si bien el código no parece complejo, la multiplicidad de tareas manuales repetitivas lleva por un lado a la proliferación de errores y por otro a que el poco código importante sea más difícil de visualizar.

A continuación presentamos un ejemplo de código sin binding:

public class SumadorWindow extends SimpleWindow {
     private TextBox text1, text2;
     private Label result;
     public void createContents(Panel mainPanel) {
         this.text1 = new TextBox(mainPanel);
         this.text2 = new TextBox(mainPanel);
         new Button(mainPanel).setCaption("Sumar").onClick(new MessageSend(this, "sumar"));
         this.result = new Label(mainPanel);
     }
     public void sumar() {
         int op1, op2
         try {
             op1 = Integer.parseInt(text1.getValue());
         }
         catch (NumberFormatException e) {
             ... // informar error en el primer operando
         }
         try {
             op2 = Integer.parseInt(text2.getValue());
         }
         catch (...) {
             ... // informar error en el segundo operando
         }
         if (op1 != null && op2 != null) {
             this.result.setValue(Integer.toString(op1 + op2));
         }
     }
}

El mismo ejemplo con Binding:

public class SumadorWindow extends SimpleWindow {
     private int op1, op2, res;
     public void createContents(Panel mainPanel) {
         new TextBox(mainPanel).bindTo("op1");
         new TextBox(mainPanel).bindTo("op2");
         new Button(mainPanel).setCaption("Sumar").onClick(new MessageSend(this, "sumar"));
         new Label(mainPanel).bindTo("res");
     }
     public void sumar() {
         this.res = op1 + op2;
     }
}





Comments