Event Listeners Personalizados en Java

Aunque el patrón Observer está implementado nativamente en Java, a veces necesitamos realizar un manejo de eventos que se adapte mejor a nuestras necesidades.

El problema del manejo de eventos es muy sencillo: Tenemos un objeto que va a ir cambiando de estado. Sin tocar su código, debemos ser capaces de “engancharlo” a otros objetos para que estén pendientes de estos cambios de estado y actúen en consecuencia. Este “enganche” debe poder activarse y desactivarse dinámicamente durante la ejecución.
Although the pattern Observer is implemented natively in Java, sometimes we need to make an event management that suits better our needs.

The problem of event handling is very simple: We have an object that will be changing its state. Without touching its code, we should be able to “hook” to other objects that are pending status changes and act accordingly. This “hook” must be turned on and off dynamically at runtime.

Para poder implementarlo haremos uso de un objeto estático, una clase y una interfaz: El objeto estático será el encargado de velar por las relaciones entre unos y otros, y avisará de los cambios pertinentes a los objetos interesados. La clase se utilizará para pasar información durante un evento. Las interfaces harán la distinción entre objetos observados y objetos observables y el resto de objetos de la aplicación.

Pero la mejor forma de ver cómo funciona es mediante un ejemplo:

Las instancias de la clase MyCustomEvent serán las que porten la información de un observado a un observable cuando se produzca un evento. Esta clase deberá contener toda la información necesaria sobre ese evento. Por ejemplo, si el evento fuera un click del ratón, esta clase debería llevar datos tales como: coordenadas en la pantalla o el número de clicks. En concreto, nuestra clase de evento sólo contiene el objeto original que lanzó el evento.


import java.util.EventObject;
import java.util.LinkedList;
import java.util.List;

public class MyCustomEvent extends EventObject {
private static final long serialVersionUID = 7383182229898306240L;

final private MyCustomListener source;

public MyCustomEvent(MyCustomListener originalSource) {
super(originalSource);
this.source = originalSource;
}

public MyCustomListener getSource() {
return this.source;
}
}

Al objeto estático que guardará las relaciones entre observables y observados lo llamaremos MyCustomEventHandler. Es muy importante la sincronización de los métodos para evitar concurrencias en aplicaciones multihilo:


import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class MyCustomEventHandler {
private static final Log log = LogFactory.getLog(MyCustomEventHandler.class);

private static Map<MyCustomListener, Set> observables = new LinkedHashMap<MyCustomListener, Set>();

public static void fire(MyCustomEvent event) {
log.trace("fire(" + event + ")");
try {
Set observers = getObservables(source);
if (observers != null)
for (final MyCustomListener pl : observers) {
try {
pl.fire(event);
} catch (Throwable t) {
log.error(t, t);
}
}
} catch (Throwable t) {
log.error(t, t);
}
}

private synchronized static Set getObservables(
MyCustomListener source) {
return observables.get(source);
}

/**
* Registra a observer para avisarle cuando observable se modifique
*
* @param observer
* @param observable
*/
public static synchronized void register(MyCustomListener observer,
MyCustomListener observable) {
Set observers = getObservables(observable);
if (observers == null)
observers = new HashSet();
observers.add(observer);
observables.put(observable, observers);
}

/**
* Deregistra a observer para no volverle a avisar cuando observable se
* modifique
*
* @param observer
* @param observable
*/
public static synchronized void deregister(MyCustomListener observer,
MyCustomListener observable) {
Set observers = getObservables(observable);
if (observers == null)
observers = new HashSet();
observers.remove(observer);
observables.put(observable, observers);
}

}

Tanto los objetos observables como los objetos observadores tienen que implementar la interfaz MyCustomListener. Esta interfaz obliga a implementar el método fire(MyCustomEvent event). En los observables, esta función será la encargada de llamar a MyCustomEventHandler. En los observadores, esta función será llamada cuando se lance un evento de su objeto observado.

En realidad sería conveniente separar esta interfaz en dos: una para observadores y otra para observados, ya que si un objeto quiere ser a la vez observador y observado podría tener problemas con este sistema. Pero por simplificación para el ejemplo hemos optado por esta implementación.


import java.util.EventListener;

public interface MyCustomListener extends EventListener {

public void fire(MyCustomEvent event);
}

Si quisiéramos tener diferentes tipos de eventos (parecido a como funciona el MouseListener), lo único que necesitaríamos es ampliar la interfaz MyCustomListener con todas las funcionalidades que queremos que tenga:


import java.util.EventListener;

public interface MyCustomListener extends EventListener {
public void fireObjectRecycled(MyCustomEvent event);
public void fireObjectRefreshed(MyCustomEvent event);
public void fireObjectUpdated(MyCustomEvent event);
}

Así mismo, podríamos usar diferentes clases derivadas de MyCustomEvent para cada una de estas funcionalidades.

El objeto estático MyCustomEventHandler también necesitaría ser modificado para lanzar los diferentes eventos.

Referencias: Hilo donde se discute este tipo de implementación de Event Handler
To implement it we will use a static object, a class and an interface: The static object will be responsible for ensuring the relationships between observers and observables, and notify relevant changes to the objects concerned. The class is used to pass information during an event. The interfaces will make the distinction between objects observed and observable objects and any other objects on the application.

But the best way to see how it works is through an example:

The instances of the class will be those that carry MyCustomEvent information to an observable object from the observed object when an event occurs. This class should contain all the necessary information on that event. For example, if the event was a mouse click, this class should carry data such as coordinates on the screen or the number of clicks. Specifically, our event class contains only the original object that triggered the event (the observable object).


java.util.EventObject import;
java.util.LinkedList import;
import java.util.List;

public class extends EventObject {MyCustomEvent
private static final long serialVersionUID = 7383182229898306240L;

private final MyCustomListener source;

public MyCustomEvent (MyCustomListener OriginalSource) {
super (OriginalSource);
this.source = OriginalSource;
}

MyCustomListener public getSource () {
this.source return;
}
}

The static object which will keep the relations between observers and observables will be called MyCustomEventHandler. It is very important to use synchronization methods to avoid concurrency issues in multithreaded applications:


java.util.HashSet import;
java.util.LinkedHashMap import;
import java.util.Map;
import java.util.Set;

org.apache.commons.logging.Log import;
org.apache.commons.logging.LogFactory import;

MyCustomEventHandler {public class
private static final Log log = LogFactory.getLog (MyCustomEventHandler.class);

private static Map <MyCustomListener, Set > = new LinkedHashMap observable <MyCustomListener, Set > ();

public static void fire (MyCustomEvent event) {
log.trace ("fire (" + event + ")");
try {
Observers = getRemarks September (source);
if (Observers! = null)
for (final MyCustomListener pl: Observers) {
try {
pl.fire (event);
} Catch (Throwable t) {
log.error (t, t);
}
}
} Catch (Throwable t) {
log.error (t, t);
}
}

private static synchronized September getRemarks (
MyCustomListener source) {
observables.get return (source);
}

/ **
* Register to watch to alert you when observable change
*
* @ Param observer
* @ Param observable
* /
public static synchronized void register (MyCustomListener observer,
MyCustomListener observable) {
Observers getRemarks September = (observable);
if (null == Observers)
Observers = new HashSet ();
observers.add (observer);
observables.put (observable, Observers);
}

/ **
* Deregistra not become observers to alert you when observable
* Modify
*
* @ Param observer
* @ Param observable
* /
public static synchronized void deregister (MyCustomListener observer,
MyCustomListener observable) {
Observers getRemarks September = (observable);
if (null == Observers)
Observers = new HashSet ();
observers.remove (observer);
observables.put (observable, Observers);
}

}

Both objects (observable objects as observers) must implement the interface MyCustomListener. This interface requires implementing the function fire(MyCustomEvent event) . In the observable, this function will be responsible for calling MyCustomEventHandler. For observers, this function will be called when the observed object launches an event.

Actually it would be convenient to separate the interface into two: one for observers and another for observables, because if an object wants to be both observer and observed, it may have problems with this system. But for simplicity for the example we have chosen this implementation.


java.util.EventListener import;

public interface extends EventListener {MyCustomListener

public void fire (MyCustomEvent event);
}

If we have different types of events (similar to how the MouseListener), all we need to do is to expand MyCustomListener interface with all the functionality we want to have:


java.util.EventListener import;

public interface extends EventListener {MyCustomListener
public void fireObjectRecycled (MyCustomEvent event);
public void fireObjectRefreshed (MyCustomEvent event);
public void fireObjectUpdated (MyCustomEvent event);
}

So, we might use different classes derived from MyCustomEvent for each of these features.

The static object MyCustomEventHandler also need to be modified to launch the different events.

References: thread which discusses this type of implementation of Event Handler

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.