2009-10-12 13 views
10

Tengo una clase simple: la llamaré Animal. Me gustaría despedir un evento en la clase Animal y hacer que se maneje en la clase donde instalé la clase Animal. En el controlador de eventos, quiero pasar un valor enteroCómo declarar y consumir eventos en Java

¿Cómo logro algo así de simple?

Respuesta

9

Cuando se quiere evitar que hereda de una clase base java.util.Observable similar, utilizar una interfaz y dejar que sus características observables implementan o delegar los métodos de la interfaz.

Aquí es la interfaz observables:

public interface IObservable 
{ 
     void addObserver(IObserver o); 

     void deleteObserver(IObserver o); 

     void notifyObservers(INotification notification); 
} 

Aquí es una clase de ayuda que podría ser utilizado por sus características observables reales:

import java.util.ArrayList; 
import java.util.List; 


public class Observable implements IObservable 
{ 
     private List<IObserver> observers; 

     @Override 
     public synchronized void addObserver(IObserver o) 
     { 
       if (observers == null) 
       { 
         observers = new ArrayList<IObserver>(); 
       } 
       else if (observers.contains(o)) 
       { 
         return; 
       } 
       observers.add(o); 
     } 

     @Override 
     public synchronized void deleteObserver(IObserver o) 
     { 
       if (observers == null) 
       { 
         return; 
       } 
       int idx = observers.indexOf(o); 
       if (idx != -1) 
       { 
         observers.remove(idx); 
       } 
     } 

     @Override 
     public synchronized void notifyObservers(INotification notification) 
     { 
       if (observers == null) 
       { 
         return; 
       } 
       for (IObserver o : observers) 
       { 
         o.update(notification); 
       } 
     } 

} 

Una verdadera observable podría tener este aspecto:

class Person implements IObservable 
{ 
     private final IObservable observable = new Observable(); 

     @Override 
     public void setFirstName(String firstName) throws Exception 
     { 
      if (firstName == null || firstName.isEmpty()) 
      { 
        throw new Exception("First name not set"); 
      } 

      this.firstName = firstName; 
      notifyObservers(new Notification(this, getFirstNamePropertyId())); 
     } 

    @Override 
    public void addObserver(IObserver o) 
    { 
      observable.addObserver(o); 
    } 

    @Override 
    public void deleteObserver(IObserver o) 
    { 
      observable.deleteObserver(o); 
    } 

    @Override 
    public void notifyObservers(INotification notification) 
    { 
      observable.notifyObservers(notification); 
    } 

    private static final String FIRSTNAME_PROPERTY_ID = "Person.FirstName"; 

    @Override 
    public String getFirstNamePropertyId() 
    { 
      return FIRSTNAME_PROPERTY_ID; 
    } 

} 

Aquí está la interfaz del observador:

public interface IObserver 
{ 
     void update(INotification notification); 
} 

Por último, aquí es la interfaz de notificación e implementación básica:

public interface INotification 
{ 
     Object getObject(); 

     Object getPropertyId(); 
} 

public class Notification implements INotification 
{ 
     private final Object object; 
     private final Object propertyId; 

     public Notification(Object object, Object propertyId) 
     { 
       this.object = object; 
       this.propertyId = propertyId; 
     } 

     @Override 
     public Object getObject() 
     { 
       return object; 
     } 

     @Override 
     public Object getPropertyId() 
     { 
       return propertyId; 
     } 
} 
+4

Santa mierda. Eso es mucho código para simplemente implementar un evento. Creo que voy a volver corriendo a C# :). En realidad, hace lo que necesito, excepto que tendré que convertirlo a la era anterior a los genéricos (estoy escribiendo para BlackBerry, todavía están en Java 1.3. – AngryHacker

+0

Me alegro de que el código te ayude. puede usar el código del observable para todos sus observables, ya no es tanto código. Incluso puede cortar el código de notificación y simplemente seguir con java.lang.Objects. Para convertir el código a Java 1.3, creo que tiene que elimine las anotaciones @Override y use listas/colecciones basadas en Objetos. –

+1

Su clase auxiliar Observable podría lanzar una ConcurrentModificationException si un Observer notificado intenta eliminarse durante la devolución de llamada de notificación. Puede solucionar esto utilizando CopyOnWriteArrayList. – Adamski

3

Ver java.util.Observable

EDIT: enfoque basado PropertyChangeSupport de Adamski parece una mejor observable que he sugerido.

+0

Mi clase ya hereda de otra clase y no tengo ganas de agregar una clase ficticia intermedia solo para evitar la limitación de herencia única. Tiene que haber una manera más simple. – AngryHacker

+0

Me había preguntado si este podría ser el caso. No eres el único :-) ver http://java.sun.com/j2se/1.4.2/docs/api/java/util/Observable.html. La solución basada en AspectJ como menciona Andrew es una opción. Otras opciones son incorporar el patrón observable en tu clase Animal (no es todo el código) o contener y observar dentro de Animal y delegar en él. Para una solución reutilizable, AspectJ parece ser la única opción. –

+0

Usar delegación. –

0

Hay también los grandes primavera bibliotecas, que proporcionan fuera de la caja de un marco caso.

5

Una interfaz simple evento tiene el siguiente aspecto:

public interface AnimalListener { 
    public void animalDoesSomething(int action); 
} 

Animal necesita para gestionar sus oyentes:

public class Animal { 
    private final List<AnimalListener> animalListeners = new ArrayList<AnimalListener>() 
    public void addAnimalListener(AnimalListener animalListener) { 
     animalListeners.add(animalListener); 
    } 
} 

Su Animal clase -Creación tiene que hacer esto:

public class AnimalCreator implements AnimalListener { 
    public void createAnimal() { 
     Animal animal = new Animal(); 
     animal.addAnimalListener(this); // implement addListener in An 
    } 
    public void animalDoesSomething(int action) { 
     System.ot.println("Holy crap, animal did something!"); 
    } 
} 

Ahora Animal puede disparar eventos.

public class Animal { 
    .... 
    public void doSomething() { 
     for (AnimalListener animalListener : animalListeners) { 
      animalListener.animalDoesSomething(4); 
     } 
    } 
} 

que se parece a un montón de código para algo tan simple como “eventos de disparo”, pero tal vez arranques, no es nada sencillo. :)

Por supuesto, hay varias extensiones de este mecanismo simple.

  • Siempre hago que mis oyentes de eventos extiendan java.util.EventListener.
  • El primer parámetro para cada método de escucha debe ser la fuente del evento, es decir, public void animalDoesSomething(Animal animal, int action);.
  • La gestión de oyentes registrados y la activación de eventos se puede abstraer a algún tipo de clase de gestión de oyentes de eventos abstractos. Mira PropertyChangeSupport para saber a qué me refiero.
2

Las clases Observer/Observable han estado en Java desde el día 1. Desafortunadamente los diseñadores originales se equivocaron un poco. Para ser justos, no tuvieron la oportunidad de aprender de 10 años de experiencia en Java ...

Resolvo su problema con la delegación. Tengo mi propia implementación de Observer/Observable, y lo recomiendo. Pero aquí hay un enfoque que funciona:

import java.util.Observable; 
import java.util.Observer; 

public class Animal { 

    private final ImprovedObservable observable = new ImprovedObservable(); 

    public void addObserver(Observer o) { 
     observable.addObserver(o); 
    } 

    public void notifyObservers() { 
     observable.notifyObservers(); 
    } 

    public void doSomething() { 
     observable.setChanged(); 
     observable.notifyObservers(new AnimalEvent()); 
    } 


} 

// simply make setChanged public, and therefore usable in delegation 
class ImprovedObservable extends Observable { 
    @Override 
    public void setChanged() { 
     super.setChanged(); 
    } 
} 

class AnimalEvent { 
} 
21

Suponiendo que el número entero que se pasa es parte del estado de la clase de animal, una forma idiomática para hacer esto en lugar de escribir un montón de su propio código es para disparar un PropertyChangeEvent. Puede utilizar la clase PropertyChangeSupport a ello, la reducción de su código para esto:

public class Animal { 
    // Create PropertyChangeSupport to manage listeners and fire events. 
    private final PropertyChangeSupport support = new PropertyChangeSupport(this); 
    private int foo; 

    // Provide delegating methods to add/remove listeners to/from the support class. 
    public void addPropertyChangeListener(PropertyChangeListener l) { 
    support.addPropertyChangeListener(l); 
    } 

    public void removePropertyChangeListener(PropertyChangeListener l) { 
    support.removePropertyChangeListener(l); 
    } 

    // Simple example of how to fire an event when the value of 'foo' is changed. 
    protected void setFoo(int foo) { 
    if (this.foo != foo) { 
     // Remember previous value, assign new value and then fire event. 
     int oldFoo = this.foo; 
     this.foo = foo; 
     support.firePropertyChange("foo", oldFoo, this.foo); 
    } 
    } 
} 

Por último, Yo aconsejaría contra el uso de Observer/Observable, ya que hace que el código ilegible/difícil de seguir: Usted está constantemente tener a compruebe el tipo de argumento pasado al Observer usando instanceof antes de bajarlo, y es difícil ver qué tipo de evento espera una implementación específica de Observer mirando su definición de interfaz. Mucho mejor para definir implementaciones de oyentes específicos y eventos para evitar esto.

+1

Esto funcionó muy bien. Para completar, quiero agregar que cualquier clase que intente actuar sobre el evento necesita crear un nuevo PropertyChangeListener, luego agregar ese oyente a través de 'animal.addPropertyChangeListener (newlyMadePropertyChangeLisener);' – MattSayar

+0

Esto es exactamente lo que quería y usar me ayudó a comprender las clases onClickListener y TabListener de Android. [Este ejemplo del mundo real] (http://accu.org/index.php/journals/1524) me ayudó a descubrir cómo implementarlo realmente. – Noumenon

+1

Puede simplificar el 'setFoo()' -method a 'void setFoo (int foo) {support.firePropertyChange (" foo ", this.foo, this.foo = foo); } '...firePropertyChange y las características Java comunes harán lo que hiciste arriba. – Erk

3

creo que la solución más simple de todos ellos se ha perdido un poco ...

Usted no necesita más que eso en el 95% de la casos:

public class Aminal extends Observable { 
    public void doSomethingThatNotifiesObservers() { 
     setChanged(); 
     notifyObservers(new Integer(42)); 
    } 
} 

que supongo que no tendrá el boxeo auto, así que hice un objeto Integer, pero parte de eso, la clase Observable es de JDK1.0 por lo que debe estar presente en su versión de Java.

Con autoboxing (en Java 1.5 y posterior) la llamada notifyObservers se vería así:

notifyObservers(42); 

Con el fin de capturar el evento enviado es necesario implementar la interfaz Observer:

public class GetInt implements Observer { 
    @Override 
    public void update(Observable o, Object arg) { 
     if (arg instanceof Integer) { 
      Integer integer = (Integer)arg; 
      // do something with integer 
     } 
    } 
} 

A continuación, agregará GetInt a la clase Animal:

Animal animal = new Animal(); 
GetInt getint = new GetInt(); 
animal.addObserver(getint); 

Esto es todo Java estándar y la clase Observable implementa todo el manejo de observadores que necesita.

Si tiene que ser capaz de desencadenar Observable desde fuera, ir con una solución de Steve McLeod, pero en mi experiencia que desea que su clase de manejar lo que sabe y dejar que otras clases de interactuar con él a través de los Observer interfaces.

Lo único que debe tener en cuenta es que debe llamar al setChanged() antes de o Observable no enviará ningún evento.

Creo que esto es para que pueda llamar a varios setChanged() y luego notificar a los observadores una sola vez, haciéndoles saber que la clase ha cambiado y dejando que ellos entiendan cómo.

También es una buena forma de eliminar al observador una vez que la clase donde se creó ya no es necesaria, esto para evitar que el observador se quede en el observable. Solo la basura que recoge la clase de observador no lo eliminará de lo observable.

Si desea observar la clase por propiedad, le recomiendo ir con PropertyChangeSupport según lo descrito por Adamski arriba. También hay un VetoableChangeSupport que puede usar si desea que los oyentes puedan bloquear un cambio.

Cuestiones relacionadas