Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 741
AngryHacker Avatar asked Oct 12 '09 06:10

AngryHacker


6 Answers

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.

like image 192
Adamski Avatar answered Oct 22 '22 15:10

Adamski


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;
        }
}
like image 44
Frank Grimm Avatar answered Oct 22 '22 14:10

Frank Grimm


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.
like image 40
Bombe Avatar answered Oct 22 '22 15:10

Bombe


See java.util.Observable

EDIT: Adamski's PropertyChangeSupport based approach seems better Observable one that I suggested.

like image 35
Miserable Variable Avatar answered Oct 22 '22 15:10

Miserable Variable


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.

like image 3
Erk Avatar answered Oct 22 '22 16:10

Erk


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 {
}
like image 2
Steve McLeod Avatar answered Oct 22 '22 14:10

Steve McLeod