Events in Java (Second try)

Wednesday, June 3. 2009

Events in Java (Second try)

Well, I'm not that satisfied with the Signal/Slot system I described in a previous article. The reflection part of it makes me itch. So I gave it a second try...

First of all lets summarize the problems with the listener pattern:

  • Two classes (The data container for the event and the listener interface) must be created for each event.
  • At least three methods must be created for each event (Register, Unregister and Fire) in each class which is going to fire the event

So it's simply too much boiler-plate code and the goal is to put as much as possible of it into some library classes without losing any type-safety and without using reflection. I'm going to describe my solution in this article and again I will use the nonsense example of a citizen which drinks some beverage and a Big Brother which monitors it.

The main method

As in my previous article we begin with the main method of the example:

public static void main(String[] args)
{
    Citizen winston = new Citizen("Winston");
    Citizen julia = new Citizen("Julia");
    
    BigBrother bigBrother = new BigBrother();
        
    winston.drinkMessageRegistry.register(bigBrother);
    julia.drinkMessageRegistry.register(bigBrother);
        
    winston.drink("milk");
    julia.drink("tea");
}

Looks pretty the same as in my previous article. This time there is something called Registry here instead of a connect method. This drinkMessageRegistry is a public member of the Citizen class. It's the point where others can register and unregister their event handlers. Let's take a look at the Citizen class to see how this looks like:

The Citizen class
public class Citizen
{
    private DrinkMessage.Sender drinkMessageSender = new DrinkMessage.Sender();
    public final MessageRegistry<DrinkMessage.Receiver> drinkMessageRegistry =
        new MessageRegistry<DrinkMessage.Receiver>(this.drinkMessageSender);

    public String name;
    
    public Citizen(String name)
    {
        this.name = name;
    }

    public void drink(String what)
    {
        System.out.println("Citizen " + name + " drinks " + what);
        this.drinkMessageSender.send(new DrinkMessage(this, what));
    }
}  

As you can see the Citizen class has two members which are needed for the event system. A private DrinkMessage.Sender and a public MessageRegistry for this sender. The Registry simply wraps the sender and hides the send() method defined by the Sender class. In this way only the Citizen class can send events through this Sender. If you don't care about that then you can leave out the Registry and simply make the Sender public and use it as the Registry, too.

In the drink method you can see how the event is fired. It simply instantiates the event container DrinkMessage and sends it through the Sender.

Now lets see how the event is received:

The BigBrother class
public class BigBrother implements DrinkMessage.Receiver
{
    public void receive(DrinkMessage message)
    {
        String name = message.citizen.name;
        String drink = message.drink;

        if ("milk".equals(drink))
        {
            System.out.println("Big Brother thinks it's ok if " + name
                + " drinks " + drink);
        }
        else
        {
            System.out.println("Big Brother calls the police because " + name
                + " drinks " + drink);
        }
    }
}

This class simply implements the Receiver interface of the event container. That's it.

Now comes the ugly part of the solution. The event container class which still contains some boiler-plate code:

The DrinkMessage class
public class DrinkMessage implements Message
{
    public interface Receiver extends MessageReceiver
    {
        public void receive(DrinkMessage message);
    }

    public static class Sender extends MessageSender<DrinkMessage, Receiver>
    {
        protected void call(Receiver receiver, DrinkMessage message)
        {
            receiver.receive(message);
        }
    }
   
    public Citizen citizen;
    
    public String drink;
    
    public DrinkMessage(Citizen citizen, String drink)
    {
        this.citizen = citizen;
        this.drink = drink;
    }
}

As you can see the event container class also defines the Sender and the Receiver as static classes/interfaces. This has two advantages: You don't need to invent two more names because in that way they always have the same name prefixed with the event name. The second advantage is that you just need to copy such an event container class to create a new one and a good IDE like Eclipse will automatically rename the references to the event container class. This means you never must edit the remaining boiler-plate code because your IDE is handling it quite well if you use an existing event container class as a template for new ones.

I know it's a shame that this little bit boiler-plate code still exists but you can blame Java Generics for it. We could remove the Receiver interface when Java would let us implement the same interface with different type parameters. But that's not possible. And we could remove the call method in the Sender class or maybe even get rid of the class completely if Java would let us cast to a generic type. But that's also not possible. Sometimes type-erasure is simply too bad...

The rest

Now there still is something missing. Yes, the library classes and interfaces containing the rest of the code which is now no longer boiler-plate code because it is written once and never touched again. Message and MessageReceiver are simply empty marker interfaces. So I'm not going to show the code here.

The MessageSender provides the methods for registering, unregistering event handlers and firing the event through the send() method:

public abstract class MessageSender<M extends Message, L extends MessageReceiver>
{
    private List<L> receivers = new ArrayList<L>();

    public void register(L receiver)
    {
        receivers.add(receiver);
    }
    
    public void unregister(L receiver)
    {
        receivers.remove(receiver);
    }
        
    protected abstract void call(L listener, M message);
    
    public void send(M message)
    {
        for (L receiver: receivers) call(receiver, message);
    }
}

The MessageRegistry class simply wraps a MessageSender and hides the send() method so outside classes can not send events but can only register and unregister event handlers:

public class MessageRegistry<T extends MessageReceiver>
{
    private MessageSender<?, T> sender;
    
    public MessageRegistry(MessageSender<?, T> sender)
    {
        this.sender = sender;
    }
    
    public void register(T receiver)
    {
        sender.register(receiver);
    }
    
    public void unregister(T receiver)
    {
        sender.unregister(receiver);
    }
 
Conclusion

This system works pretty well. It is compatible to the Listener Pattern, uses no reflection and therefor has no performance penalty, it is type-safe and it has a minimum boiler-plate code left. Creating a new event simply means copying an existing event container class for using it as a template and adding two class members (For the sender and the registry) to the class which is going to fire the event.

Posted in Java | Comments (0)

Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.