Implementando Observers en Java
Una aclaración antes de empezar: en Java existe un mecanismo para implementar Observers que requiere que la clase observada extienda Observable. Esto, en algunas situaciones puede ser una limitante ya que no es posible extender mas de una clase. Si por ejemplo, queremos observar una clase que ya extiende a otra, no seria posible incorporar este mecanismo con las herramientas built in de Java sin hacer cambios importantes. En esta situación, la mecanica que se explica a continuación puede ser de utilidad.
En muy pocas palabras, los patrones de diseño son soluciones conocidas para problemas conocidos, un esfuerzo por no reinventar la rueda cada vez que nos enfrentamos a un problema cotidiano en el mundo de la programación. Este patrón se utiliza cuando queremos que alguna entidad, se entere de que algo pasó en alguna otra parte de nuestro sistema. Por ejemplo, supongamos que tenemos una ventana con un cierto listado de datos, como la siguiente:
Los datos que la tabla muestra pueden cambiar en función de acciones que se realizan en algún otro formulario de la aplicación que permita el ingreso o edición y el objetivo es que ésta tabla se actualice cuando esos datos cambian sin necesidad de que el usuario que hace uso de ella tenga que explícitamente recargar o refrescar los datos.
Este es un caso típico en que el patrón Observer nos será de gran utilidad y será la tabla de datos quien deberá observar los eventos que ocurran en el formulario de edición.
Además de los formularios para editar y mostrar los datos, la solución constará de dos clases adicionales, una interfase IObserver y una clase que hace de nexo entre las distintas partes que componen la solución y que llamaremos ObserverServer. La interfase IObserver deberá ser implementada por aquellas entidades que están observando a otras y contendrá los métodos necesarios para comunicar los cambios:
public interface IObserver {
public void notifyEvent( String eventName, HashMap parameters );
}
La clase interesada en la actualización de datos deberá implementar el método notifyEvent y deberá suscribirse ante el ObserverServer como observador del evento de actualización de datos. Veamos el ObserverServer para aclarar esta situación:
public class ObserverServer {
private ObserverServer instance = null;
private HashMap events;
private ObserverServer getInstance() {
if( instance == null ) instance = new ObserverServer();
return instance;
}
private ObserverServer() {
events = new HashMap();
}
public void registerTo( String eventName, IObserver caller ) {
LinkedList callers = (LinkedList) events.get(eventName);
if( callers == null ) {
callers = new LinkedList();
callers.add(caller);
events.put(eventName, caller);
} else {
callers.add(caller);
}
}
public void fireEvent( String eventName, HashMap parameters ) {
LinkedList callers = (LinkedList)events.get(eventName);
if( ! (callers==null) ) {
Iterator it = callers.iterator();
IObserver caller;
while( it.hasNext() ) {
caller = (IObserver)it.next();
caller.notifyEvent(eventName, parameters);
}
}
}
Dos precisiones sobre esta clase:
1) La calse es un singleton. Esto es necesario ya que todas las entidades que se comunican a través de ella deben acceder a la misma lista de eventos
2) La lista de eventos se modela como un HashMap (events) que contiene para cada evento una LinkedList con las entidades que esperan ser notificadas cuando el evento ocurra. Esto podría alterarse en función de necesidades específicas.
La clase ObserverServer cuenta con dos métodos, uno para suscribirse como observador de un evento, registerTo y otro para iniciar las notificaciones ante la ocurrencia de un evento, fireEvent.
El método registerTo recibe como parámetros el nombre del evento y una referencia a la clase que debe ser notificada y que implementa la interfase IObserver. El método simplemente agrega la referencia al observador en la lista correspondiente y en caso de que se la primera vez que una entidad se suscribe a dicho evento, creará una nueva lista. Nótese que no hay ningún tipo de control sobre los eventos a los que una entidad se registra. Esto podría cambiar según las necesidades especificas de lo que se está implementando, pero para mostrar el funcionamiento básico del patrón no es necesario agregar tal complejidad al ejemplo.
El método fireEvent recibe como parámetros el evento que ocurrió y una HashMap de parámetros relativos a dicho evento. En este caso, se recorrerá la lista de entidades suscritas al evento y para cada una de ellas se invocará el método notifyEvent. Nuevamente, aqui no hay ningún control que verifique que el evento sea válido. Quien dispara el evento y quien está suscrito a él deberán acordar los parámetros que se envían en el HashMap. El ObserverServer solo actúa como vehículo para la comunicación y no impone ningún tipo de restricción a los parámetros.
Veamos escuetamente como se usa todo esto para algo útil
Nuestro formulario de despliegue de datos quiere ser notificado cuando los datos cambian para mostrar la nueva realidad, por lo que va a suscribirse a un evento de notificación en su constructor
public class ContainerList implements IObserver {
private ObserverServer obs;
public ContainerList() {
obs = ObserverServer.getInstance();
obs.registerTo("ContainerUpdate",this);
}
...
public void notifyEvent( String eventName, HashMap parameters ) {
if( eventName.equals("ContainerUpdate") ) {
...
}
}
}
El formulario que muestra los datos se suscribe al evento ContainerUpdate y pasa una referencia a sí mismo. Cuando el evento ocurra, el ObserverServer invocará el evento notifyEvent el cual implementa la actualización de datos necesaria.
La otra parte de esto, el formulario de edición de datos deberá avisar cada vez que una modificación ocurrra
public class FrmContainerUpdate() {
public ObserverServer obs = ObserverServer.getInstance();
...
private SaveData() {
// Proceso los datos del formulario como normalmente lo haría
// y notifico del evento
HashMap params = new HashMap();
params.put("containerId",id); //Agrego todos los parametros necesarios al HashMap
obs.fireEvent("ContainerUpdate", params);
}
De esta manera, cada vez que ocurre una actualización de los datos de un contenedor, la tabla de datos es actualizada.
Este patrón es útil en una gran cantidad de situaciones que las que se quiere comunicar a varias entidades, sin embargo, no debe ser confundido con un mecanismo para la comunicación entre procesos en el que existe un flujo de información más o menos continuo, para eso existen otras herramientas.


Comentarios recientes