Some time ago I tried to learn Java. I have read a couple of good books on the subject I think everyone has his or her own favorite Java book. Java provides programmers with some fine idioms. Some idioms result from the language itself, and some are part of the standard Java library. One of these idioms is the event publishing scheme taken by the Swing library to enable event-driven GUI programming.
In Swing, when you need a button on your window, you create it, add the button object to the "content pane" (the logical part of the window that manages its elements), and you are done. Almost. You are not usually interested in buttons that do nothing, so you add the "event handling" to it. Swing associates an action with every element of the window (for example, the action for the button is "press") and you can bind your own code with that action. This is done in two steps:
1. Write a class that implements an ActionListener
interface. To do
that, you need to implement its method, actionPerformed
. Put all the
code that needs to be executed in this method.
2. Create an object of that class and pass it to the button object. In other words, register it.
Listing One presents a very simple Java program that creates a window on the screen with one button. The event-handler for the "press" action of the button prints a message on the console window. The "press" event handler is not the only event handler, though. The second event handler is the handler for an event associated with the window being closed that causes the whole application to terminate. The whole program can be written in much more compact form (thanks to anonymous inner classes [1]), but I wanted to expose the idea of registering event consumers to event producers. This idiom is not restricted to Java (nor to Swing for that matter). Java uses this idiom quite heavily, and the code presented in Listing One uses only a fraction of the possibilities.
Listing One
import javax.swing.*; import java.awt.*; import java.awt.event.*; public class MyJavaApp { public static void main(String[] args) { // create a button JButton button = new JButton("Press me!"); // register the event handler for pressing the button button.addActionListener(new MyButtonListener()); // organize the layout of the window JPanel pane = new JPanel(); pane.add(button); JFrame frame = new JFrame("My Application"); frame.getContentPane().add(pane, BorderLayout.CENTER); // register the event handler for closing the window frame.addWindowListener(new MyWindowListener()); frame.pack(); frame.setVisible(true); } } // this class implements the event handler for a button class MyButtonListener implements ActionListener { public void actionPerformed(ActionEvent e) { System.out.println("I was pressed!!!"); } } // this class implements the event handler for // closing the window class MyWindowListener extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } }
If this idiom is not restricted to any particular language, it would be nice to implement it in C++.
Brute-Force Approach
The first try to implement the concept of event listeners may look like the one presented in Listing Two.
Listing Two
<pre> // brute-force approach to the // event producer-consumer idiom #include <iostream> #include <ostream> #include <string> #include <vector> using namespace std; // interface of the listener class ActionListener { public: virtual void actionPerformed() = 0; }; // some class that can generate events class SomeClass { public: // registration function void addListener(ActionListener *p) { listeners_.push_back(p); } // some other functions // ... void foo(); private: // collection of pointers to listeners typedef vector<ActionListener*> ListenerBag; ListenerBag listeners_; // some other members // ... }; // some function that can produce an event void SomeClass::foo() { // ... // produce an event and notify // all listeners that are registered ListenerBag::iterator it; ListenerBag::iterator itend = listeners_.end(); for (it = listeners_.begin(); it != itend; ++it) { (*it)->actionPerformed(); } } // some listener class class MyListener : public ActionListener { public: MyListener(const string &name) : name_(name) {} void actionPerformed() { cout << name_ << " : Action performed, sir!" << endl; } private: string name_; }; int main() { MyListener l1("listener 1"); MyListener l2("listener 2"); SomeClass someobject; someobject.addListener(&l1); someobject.addListener(&l2); // ... someobject.foo(); // ... return 0; }
This approach has a couple of inherent problems that come with its design (these are only the problems I can find — there may be others):
1. It is not generic with respect to the interface. There is only one interface,
ActionListener
. In a non-trivial project, there can be a need for more
than just one listener interface.
2. It is not generic with respect to the information being passed with each
event. Here, the actionPerformed
method does not accept any parameters,
so it can be used only to notify of an event taking place. In practice,
different events need to carry different amounts of information. For example,
the information associated with an event may be the
coordinates of a point on the screen where the mouse clicked, or it may be temperature
data, such as the data produced by sensors in the core of a nuclear power station.
There is no way of fixing the type of this event information.
3. It is not generic with respect to the interface vocabulary. In Listing One, the ActionListener
interface has only one function signature. It
is quite constraining for clients that are interested in different events, for
example, not only in left-clicks but also in cursor movement. In the GUI, the
window can produce many different events, like close clicked, minimalize,
maximalize, and so on.
4. It does not scale up. This problem can be seen when one producer can be a source of an event defined by different listener interfaces. The brute-force approach imposes the need to implement as many collections in the event producer class as there are listener interfaces. Clearly, this approach does not promote code reuse.
5. It is not thread-safe. The STL collections are not thread-safe by their
definition and SomeClass
does nothing to change it. When two listener
objects try to register themselves with the instance of the SomeClass
class, the undefined behavior is waiting for them because the push_back
method cannot be executed concurrently.
Is it THAT bad? Not quite. There is one big advantage of this brute-force strategy:
it works. This should be the clue that it does not deserve to be thrown away
at this stage. This strategy includes a couple good ideas. One good idea is
the structure of event generator (event source). In Listing Two, it manages the collection of listeners (by pointers) that will be notified
when the event takes place. Each listener can register himself with the event
source by one of its public members (addListener
). For the interface
to be complete, the event source also needs a reverse function like removeListener
so that listeners can notify it that they are no longer interested in its events.
This is the core of what is needed for implementing this Java-like communication idiom.