An Introduction to Event-Based Programming
How to write your own event dispatcher and make use of this simple yet powerful pattern beyond GUI programming


There is a strange asymmetry in the popularity of event-driven programming: As a means to connect GUI components with the „business logic“ of an application, the concept is widely accepted and well-known even among rookie programmers. In fact, implementing event handlers that react to mouse, keyboard or touch interactions with some visual element is one of the first things that people learn when they begin programming dynamic web pages with JavaScript, or basically any sort of graphical application using a GUI toolkit like WinForms Qt, WPF/XAML or Android Studio.

However, the typical usage of event-driven programming is a one-way road: Coders embrace the concept of events in GUI libraries (and a few other APIs) and happily write event handler functions, but they usually don't define their own events or implement their own event systems to solve programming problems in other areas. It seems like event systems as a whole are widely regarded as so „mysterious“ that most people don't understand (and don't even think about) why and how to make full use of them. They appear to be so far off from the common paths of thinking of even experienced developers that the topic wasn't even mentioned in any of the computer science lectures I attended at university. That's sad and unnecessary, because event systems are not only very powerful, but also very easy to understand and implement. In many programming languages, you can write your own simple, but nevertheless highly useful events system with only a few lines of code and basic knowledge of object-oriented programming concepts. Let's take a look.

The Basic Principles of Event-Driven Programming

The fundamental idea behind event-driven programming is to break up a hard-wired program flow defined by nested function calls and replace it with a much a more flexible system – a system in which parts of a program notify the rest of the application about the incidence of an „event“ without defining what should happen as a consequence. Other parts of the program can then „listen“ to events and react on them in any desired way. This concept is called „loose coupling“. It gives you the power to connect software components in a ways that were not considered when the components were originally written, or where hard-wiring is not desirable in order to keep components reusable and independent of each other. For example, an event system of some sort is a fundamental part of any software architecture that can be extended with „plug-ins“.

In some ways, event-driven programming can feel a bit like multi-threading, since reactions to events typically make the program flow "jump" a lot between different functional contexts. This creates the impression that separate parts of the program run simultaneously. However, in its basic form, an event-driven program is still single-threaded and executes everything sequentially. This means that you don't need to worry about all the issues that can make multi-threaded programming very difficult to do right.

Implementation Concept

Event systems can be implemented in many different ways, from very simple to very complex. In this article I'd like to introduce to you a minimal solution that can easily implemented in many languages that support object-oriented programming, like C#, Java, JavaScript, PHP, Python and others.

The central piece of this system is a class called event dispatcher. When components do no longer communicate directly with each other, but instead through events, they need something that acts as a broker to pass the events between them. This is what the event dispatcher does. More specifically, the event dispatcher is responsible for the following tasks:

  • For each event, it keeps a list of functions called „event handlers“ that have „subscribed“ to that specific event. This means that they are called whenever the event happens. The event dispatcher also provides methods to add (subscribe) and remove (unsubscribe) handlers to/from the list.
  • It provides a method to trigger or „fire“ an event. As an argument, this method takes a „payload“ object that is passed to the subscribers. The payload object can contain additional information to help the handler functions process the event. When an event is triggered, the event dispatcher simply loops though the list of subscribed handler functions and calls each of them, passing the payload.

That's it already. The actual implementation varies a bit depending on the used programming language, requirements and personal taste. The following code listings show how such a minimal event sytem can look like in Python and in Java. If you use another programming language, these examples should still help you to grasp the concept.

A Simple Event Dispatcher in Python


class EventDispatcher:
    
    def __init__(self):
        # Create a dictionary for the subscription lists.
        # They keys will be event names, the values will
        # be the subscription lists for each event:
        self.listeners = dict()
    
    def addListener(self, eventName, listener):        
        # If there's no subscription list for 
        # the passed event yet, create one:
        if not eventName in self.listeners:
            self.listeners[eventName] = []
        
        # If the passed listener is not on the
        # subscription list yet, add it:
        if not listener in self.listeners[eventName]:
            self.listeners[eventName].append(listener)
    
    def removeListener(self, eventName, listener):
        if not eventName in self.listeners:
            return
        
        if listener in self.listeners[eventName]:
            self.listeners[eventName].remove(listener)
    
    def fire(self, eventName, payload):
        
        # If passed event is unknown, abort here:
        if not eventName in self.listeners:
            return
        
        # Otherwise, call all registered subscribers:
        for listener in self.listeners[eventName]:
            listener(payload) 

How to use it


# Create event dispatcher instance:
ed = EventDispatcher()

# Define an event handler function:   
def handleMyEvent(message):
    print "I'm a function and I just received a message: ", message

# Register event handler for the "myEvent" event:
ed.addListener("myEvent", handleMyEvent)

# Fire the "myEvent" event:
ed.fire("myEvent", "Hello world!")

# Remove the stand-alone function from the subscribers list:
ed.removeListener("myEvent", handleMyEvent)

# Now, let's create an event handler-handing class:   
class MyEventHandlerClass:
    def handleMyEvent(self, message):
        print "I'm an object method and I just received a message:", message

# Create an instance of our event handler class:
myObject = MyEventHandlerClass()

# Register object method as event handler for the "myEvent" event:
ed.addListener("myEvent", myObject.handleMyEvent)

# Fire the "myEvent" event:
ed.fire("myEvent", "Hello universe!")

A Simple Event Dispatcher in Java


public class Easyvents {

    HashMap<String, ArrayList<Consumer();

    public void addListener(String type, Consumer<Object> listener) {
        
        if (!mListeners.containsKey(type)) {
            mListeners.put(type, new ArrayList<Consumer<Object>>());
        }
        
        if (!mListeners.get(type).contains(listener)) {
            mListeners.get(type).add(listener);
        }
    }

    public void removeListener(String type, Consumer<Object> listener) {
        
        if (!mListeners.containsKey(type)) {
            return;
        }
        
        if (mListeners.get(type).contains(listener)) {
            mListeners.get(type).remove(listener);
        }
    }

    public void fire(String type, Object payload) {
        
        if (mListeners.get(type) == null) {
            return;
        }
        
        for (Consumer<Object> listener : mListeners.get(type)) {
            listener.accept(payload);			
        }
    }
}

How to use it


public class Main {
    public static void main(String[] args) {
        
        EventDispatcher events = new EventDispatcher();

        events.addListener("myEvent", Main::handleMyEvent);
        events.fire("myEvent", "Hello World!");
    }

    public static void handleMyEvent(Object payload) {
        System.out.println("Event system says: " + (String) payload);
    }
}

Just like in Python, you can of course also wire up object instance methods as event handlers by simply replacing "Main::handleMyEvent" with something like "myObject.handleMyEvent".

You can find both the Python and the Java example on GitHub:

Use Cases

So what can you do with your new minimalistic event system? The possibilities are practically endless, and the more you use it, the more ways you'll find to make use of it in future projects. To get you started, here are a few examples of how event systems helped me to solve various real-world coding problems:

Example 1: Attaching a GUI to a Library that Contains „Business Logic“

This example is basically the inverse case of how events are typically used in GUI programming: Instead of having business logic that reacts to GUI element events, I had a situation where I needed GUI elements (a progress bar and a status text label) to be updated whenever some state variables in the business logic changed during a lengthy computation. The business logic is implemented as a (self-written) library that is also used by other applications that don't have the same (or any) GUI elements. Obviously, hard-coding method calls into the library code to update the GUI elements was not an option here.

One solution would have been to create a separate thread with a loop that continuously checks the status variables and updates the GUI elements whenever necessary. However, this would have meant to add unnecessary complexity and inefficiency to the program. With events, I was able to solve the problem in a very simple and elegant way: I added a minimal event system and modified the library code to fire an event whenever the status varibales were changed. The actual values of the status variables are passed along with the fired events as payload data. This way, the library code remained independent of the GUI: It only contains a few calls to trigger events, with no assumptions about how and by what they are handled. As long as no event handlers are registered, nothing happens. When the library is used together with the GUI, the GUI code registers event handler functions that receive the fired events (and the status variables as payload data) and update the GUI elements.

Example 2: Forward Incoming Data to Different Application Modules

I once worked on an application that consists of multiple modules and processes data that is frequently received over a network conncetion. Each module needs to process each incoming data packet (or at least take a look at it), and it should be easily possible to add additional modules at any time. This is the classic "plug-in architecture" example: Using an event system, each module registers a handler function that reacts to the "incoming data" event. When new data arrives, the application's core fires that event and passes the data to each registered module.

That's it. As you see, event systems are really easy to implement and often provide a simple solution for programming problems that can otherwise be difficult to solve in a clean way.