How to declare and consume events in Java
I have a simple class - will call it Animal. I'd like to fire off an event in the Animal class and have it handled in the class where I instantiated the Animal class. In the event handler, I want to pass an Integer value
How do I pull off something simple like开发者_运维百科 that?
Assuming that the integer being passed is part of the Animal class state, an idiomatic way to do this rather than writing lots of your own code is to fire a PropertyChangeEvent
. You can use the PropertyChangeSupport
class to do this, reducing your code to this:
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);
}
}
}
Finally, I would advise against using Observer
/ Observable
as it makes code unreadable / difficult to follow: You are constantly having to check the type of the argument passed to the Observer
using instanceof
before downcasting it, and it's difficult to see what type of event a specific Observer implementation is expecting by looking at its interface definition. Much nicer to define specific listener implementations and events to avoid this.
When you want to avoid inheriting from a java.util.Observable-like base class, use an interface and let your observables implement or delegate the interface's methods.
Here is the observable interface:
public interface IObservable
{
void addObserver(IObserver o);
void deleteObserver(IObserver o);
void notifyObservers(INotification notification);
}
Here is a helper class that could be used by your real observables:
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);
}
}
}
A real observable could look like this:
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;
}
}
Here is the observer interface:
public interface IObserver
{
void update(INotification notification);
}
Finally, here is the notification interface and a basic implementation:
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;
}
}
A simple event interface looks like this:
public interface AnimalListener {
public void animalDoesSomething(int action);
}
Animal
needs to manage its listeners:
public class Animal {
private final List<AnimalListener> animalListeners = new ArrayList<AnimalListener>()
public void addAnimalListener(AnimalListener animalListener) {
animalListeners.add(animalListener);
}
}
Your Animal
-creating class needs to do this:
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!");
}
}
Now Animal
can fire events.
public class Animal {
....
public void doSomething() {
for (AnimalListener animalListener : animalListeners) {
animalListener.animalDoesSomething(4);
}
}
}
That looks like a lot of code for something as simple as “firing events” but maybe firing events isn’t simple at all. :)
Of course there are various extensions to this simple mechanism.
- I always make my event listeners extend
java.util.EventListener
. - The first parameter for each listener method should be the source of the event, i.e.
public void animalDoesSomething(Animal animal, int action);
. - Management of registered listeners and event firing can be abstracted to some kind of abstract event listener management class. Look at PropertyChangeSupport to know what I mean.
See java.util.Observable
EDIT: Adamski's PropertyChangeSupport
based approach seems better Observable one that I suggested.
I believe the simplest solution of them all has been missed a bit...
You would not need more than this in 95% of the cases:
public class Aminal extends Observable {
public void doSomethingThatNotifiesObservers() {
setChanged();
notifyObservers(new Integer(42));
}
}
I'm guessing you'll have no auto boxing, so I made an Integer
object, but part from that, the Observable
class is from JDK1.0 so it should be present in your version of Java.
With autoboxing (in Java 1.5 and later) the notifyObservers
call would look like this:
notifyObservers(42);
In order to catch the sent event you need to implement the Observer
interface:
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
}
}
}
Then you'll add GetInt
to the Animal
class:
Animal animal = new Animal();
GetInt getint = new GetInt();
animal.addObserver(getint);
This is all standard Java and the Observable
class implements all the observer handling you need.
If you need to be able to trigger Observable
from outside, go with Steve McLeod's solution, but in my experience you'll want to let your class handle what it knows and let other classes interact with it via the Observer
interfaces.
The only thing you need to be aware of is that you need to call setChanged()
before notifyObservers()
or Observable
won't send any events.
I think this is so you can call several setChanged()
and then notify the observers only once, letting them know the class has changed and leaving it up to them to figure out how.
It is also good form to remove the observer once the class where it was created is no longer needed, this to prevent the observer from being left in the observable. Just garbage collecting the observer class will not remove it from the observable.
If you want to observe the class on a per property basis I'd recommend going with PropertyChangeSupport
as described by Adamski above. There's also a VetoableChangeSupport
that you can use if you want listeners to be able to block a change.
The Observer/Observable classes have been in Java since day 1. Unfortunately the original designers screwed up somewhat. To be fair, they didn't have the chance to learn from 10 years of Java experience...
I solve your problem with delegation. I have my own implementation of Observer/Observable - and I recommend this. But here's an approach that works:
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 {
}
My first suggestion here would be to look at AspectJ. This is one of the design patterns that the language is best at handling. The following article provides a very eloquent description of how this can be implemented:
http://www.ibm.com/developerworks/java/library/j-aopwork6/index.html
There is also the great Spring libraries, that provide out-of-the-box an event framework.
Guava's EventBus is another off-the-shelf alternative
精彩评论