Framework for Event-Driven Programming

Some lyrics


image
The longer I program, the more I like loosely coupled systems that consist of a large number of disparate units (modules) that do not know anything about each other, but assume the existence of others. Such systems should ideally be assembled like a constructor, without dependencies and without adapting to each other. Ideally, during the operation of such a system, all the necessary tasks should be performed without stopping the system, simply introducing a new module into the world (say, by throwing a jar in the classpath), and the system will immediately begin to interact with the new module.
In this regard, the event-driven (or Event-oriented ) programming paradigm looks very attractive .

Its indisputable advantage is that it is not necessary to edit the existing code when adding or removing a module, the system will continue to work non-stop, we will just gain or lose some of the functionality.
It should be noted that the minus is its plus - the unpredictability of behavior during changes, as well as the weakening of control over the system as a whole (who is to blame, why does it work wrong?) - unlike traditional programming, where the system responds to such anomalies immediately and harshly, with indicating the reasons (the desired module was not found).

Why change traditions


To some extent, event-oriented programming is manifested everywhere - from modern multi-tasking OS to all sorts of frameworks. Instead of each participant (module) listening to all the events, he subscribes only to those that are of interest to him, thereby consuming less machine resources. In fact, even a method call on an object can be perceived as synchronous sending and receiving messages with a guarantee of delivery and receipt. So why the extra complexity?
The answer is that everything works for any outcome. We are not interested in who received our message, how many recipients, and whether he will respond at all. We just gently notify. No one is interested - and okay. Interesting - that's great, we are working on.

Realities


For example, the same event system in Java Swing. To listen to some events, for each component there are addXXXListener, removeXXXListener methods. There is a fireXXXEvent method to send such messages. Everything is fine. But, let's say, we write our component in the same philosophy. And we want to send a variety of events or respond to them, while maintaining encapsulation. Therefore, each time it is necessary to implement these methods for each XXX event, for each component ...

Decision


The code is always similar to disgust, so I would like to replace it with a few lines. I thought, and as a result, more than one day I implemented a helper for such tasks. This will be a class with static methods that can be called from anywhere in the program. So what do we need?
Firstly, we want to respond to any events. Let, for definiteness, our events will implement the standard java.util.EventObject interface, and listeners will implement the java.util.EventListener interface. So, on the one hand, we are not limited by anything, and on the other hand, it is as simple as possible to connect this with the AWT \ Swing event paradigm. Then, perhaps, an event subscription should look like this:
public static synchronized void listen(final Class event, final EventListener listener);

A naive implementation looks like this:
		if (!Events.listeners.containsKey(event)) {
			Events.listeners.put(event, new ArrayList());
		}
		@SuppressWarnings("unchecked")
		final List list = (List) Events.listeners.get(event);
		if (!list.contains(listener)) {
			list.add(listener);
		}


So we expressed our desire to keep abreast of the events of some eventObject subclass, promising that we will implement the EventListener interface (it does not define methods, more on that later). But we will definitely be notified if a specific event occurs.

Further, for a clean exit, we need the ability to unsubscribe from an event, it’s suitable
public static synchronized void forget(final Class event, final EventListener listener);

And the trivial code:
	public static synchronized void forget(final Class event, final EventListener listener) {
		if (Events.listeners.containsKey(event)) {
			Events.listeners.get(event).remove(listener);
		}
	}


And also for a completely clean exit (although there is a controversial point here):
public static synchronized  void forget(final Class event);

And the natural code:
	public static synchronized  void forget(final Class event) {
		if (Events.listeners.containsKey(event)) {
			Events.listeners.get(event).clear();
			Events.listeners.remove(event);
		}
	}


Good on exit. It seems to be all. And, no, not all - you still need to notify one-line in many places about some particular events, it looks like this:
public static synchronized void fire(final EventObject event, final String method);

The code turned out single-threaded:
	public static synchronized void fire(final EventObject event, final String method) {
		final Class eventClass = event.getClass();
		if (Events.listeners.containsKey(eventClass)) {
			for (final EventListener listener : Events.listeners.get(eventClass)) {
				try {
					listener.getClass().getMethod(method, eventClass).invoke(listener, event);
				} catch (final Throwable e) {
					e.printStackTrace();
					throw new RuntimeException(e);
				}
			}
		}
	}

The disadvantage of the implementation, of course, is that the processing is performed sequentially, and in the calling thread. For most applications, this approach should be enough, but, apparently, it is worth organizing processing through AWTshny EventQueue or something similar. In future versions I will fix it.
Another drawback is that throwing an exception in the handler throws the exception into the method call, and informing the listeners stops (due to one “unscrupulous” listener, some others may not receive messages). In the final version, I fixed this behavior to ignore with standard logging, and optionally subscribe to exception events in handlers.

Now, instead of implementing the methods, everything may look like in the demo (for example, I chose java.awt.event.ActionEvent):
		final ActionListener listener = new ActionListener() {
			@Override
			public void actionPerformed(final ActionEvent e) {
				System.out.println(e);
			}
		};
		Events.listen(ActionEvent.class, listener);// слушаем событие
		//
		final ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "command");
		Events.fire(event, "actionPerformed");// ActionListener.actionPerformed(ActionEvent)
		//
		Events.forget(ActionEvent.class); // очистка

The only inconvenience is that in the Events.fire method, you must also specify the name of the method as a string, so that it necessarily takes one argument - the object of our event. This happened because different listeners implement different methods of responding to messages, and even one listener can have several such methods, corresponding to the type of event (It seems like MouseListener defines several methods, for example, mouseOver and mouseOut, as well as others).
Well, in conclusion, there is still repentance: all methods are statically synchronized, it is necessary to replace regular collections with thread-safe ones. The work slows down even more (in terms of nanoseconds, in comparison with a direct method call) reflection - which occurs in a cycle when an event is triggered for each listener, but I think this is a necessary evil.

Crooked, uncomfortable and unnecessary


I myself liked this approach so much that I decided to share the library with the community ( https://github.com/lure0xaos/Events.git ). If you don’t like it, put down the cons, but in general I will be glad to constructive criticism in the comments and sensible suggestions and discussions.

Also popular now: