topical media & game development

talk show tell print

object-oriented programming

Event-driven computation

subsections:


Event-driven computation underlies many applications, ranging from graphical user interfaces to systems for discrete event simulation and business process modeling. An important characteristic of event-driven computation is that control is relinquished to an environment that waits for events to occur. Handler function or handler objects are then invoked for an appropriate response.

In this section we will look at the Reactor pattern that explains the interaction between objects and the environment. We will also look at an event system, in which the event types are defined by the application programmer. In this application, events are used to maintain global consistency, similar to the Observer pattern.

The Reactor pattern

The Reactor pattern has been introduced in  [Schmidt95] as a general architecture for event-driven systems. It explains how to register handlers for particular event types, and how to activate handlers when events occur, even when events come from multiple sources, in a single-threaded environment. In other words, the reactor allows for the combination of multiple event-loops, without introducing additional threads.

The Reactor pattern

See D.C. Schmidt, Using Design Patterns to Develop Reusable Object-oriented Communication Software, CACM October '95, 38(10): 65-74


slide: The Reactor pattern

The abstract layout of the software architecture needed to realize the pattern is depicted in slide reactor-structure. The reactor environment must allow for binding handlers to particular types of events. In addition, it must be able to receive events, and select a handler to which the event can be dispatched.

slide: The Reactor pattern -- structure

Events may be organized in a hierarchy. There are two possible choices here. Either the topmost event class has a fat interface, containing all the methods that an event may ever need to support, or the topmost event class can be lean, so that additional methods need to be added by the subclasses of event. The first solution is chosen for hush, because in C++ it is not possible to load new classes dynamically. The latter solution is the way Java does it. In Java new event types can be added at the reactor level without recompiling the system. In the Java AWT and Swing libraries, handlers are called Listeners.

Concrete handlers, derived from an abstract handler, must provide a method, such as operate(Event) that can be called by the reactor when the handler is selected after receiving an event.



slide: The Reactor Pattern - interaction

The interaction between the application, its handlers, the reactor and the environment from which the events originate is depicted in slide reactor-interaction. First, the reactor must be initialized, then one or more handlers can be registered, providing a binding for particular types of events. The reactor must then start to execute its eventloop. When it receives an event from the environment, it selects a handler and dispatches the event to that handler, by calling operate(Event).

Consequences

Modularity is one of the advantages of an event-driven software architecture. Handlers can be composed easily, since their invocation is controlled by the reactor. Another advantage is the decoupling of application-independent mechanisms from application-specific policies. In other words, handler objects need not be aware of how events are dispatched. This is the responsibility of the system or framework.

The fact that control is handed over to the environment has, however, also some disadvantages. First of all, as experience with student assignments shows, it is difficult to learn in the beginning. But even when mastered, applications may be hard to debug, since it is not always clear why a particular handler was invoked, and because it may be difficult to repeat the computation preceding the fault.

Applicability

Some variant of the reactor pattern is used in Unix (X) Windows, (MS) Windows, and also GUI libraries such as Interviews, ET++ and hush. Another example is the Orbacus object request broker, that supports a reactor mode for server objects, which allows for receiving messages from multiple sources in a single thread. The Orbacus broker, however, also allows for multi-threaded servers.

Abstract event systems

To conclude this chapter about idioms and patterns, we will look at a somewhat more detailed example employing (user-defined) events to characterize and control the interaction between the objects. The example is taken from  [Henderson93]. The abstract system, or repertoire of statements indicating the functionality of our application is depicted in slide as-system.


  th = new centigrade();
  th = new fahrenheit();
  th.set(f);
  f = th.get();
  

For thermometer th, th1; float f;


slide: Abstract system -- thermometers

First, we will define the functional behavior of the system (in this case a collection of thermometers that record and display temperature values, as characterized above). Then we will introduce the user interface classes, respectively to update the temperature value of a thermometer and to display its value. After that we define a concrete event class (derived from an abstract event class) for each of the possible kinds of interactions that may occur. Then, after installing the actual objects comprising the system, we will define the dependencies between (actual) events, so that we can guarantee that interactions with the user will not result in an inconsistent state.

Functional behavior

A thermometer must provide the means to store a temperature value and allow for the changing and retrieving of this value. The temperature values are assumed to be stored in degrees Kelvin.


  class thermometer { 
thermometer
protected thermometer( float v ) { temp = v; } public void set(float v) { temp = v; } public float get() { return temp; } protected float temp; };
Since only derived classes can use the protected constructor, no direct instances of thermometer exist, so the class is abstract. We will distinguish between two kinds of thermometers, measuring temperatures respectively in centigrade and fahrenheit.


  class centigrade extends thermometer { 
centigrade
public centigrade() { super(0); } public void set(float v) { temp = v + 273; } public float get() { return temp - 273; } };
The class centigrade redefines the methods get and set according to the measurement in centigrade, and in a similar way we may define the class fahrenheit.


  class fahrenheit extends thermometer { 
fahrenheit
public fahrenheit() { super(0); } public void set(float v) { temp = (v - 32) * 5/9 + 273; } public float get() { return temp * 9/5 + 32 - 273; } };
Both the thermometer realization classes take care of performing the conversions necessary to store and retrieve the absolute temperature value.

User interface

We will define two simple interface classes, of which we omit the implementation details. First, we define the interface of the displayer class, needed to put values to the screen.


  class displayer extends window { 
displayer
public displayer() { ... } public void put(String s) { ... } public void put(float f) { ... } };
And secondly, we define a prompter class, which defines (in an abstract way) how we may get a value from the user (or some other component of the system).


  class prompter extends window { 
prompter
public prompter(String text) { ... } public float get() { ... } public String gets() { ... } };
Together, the classes displayer and prompter define a rudimentary interface which is sufficient to take care of many of the interactions between the user and the system.

Events

To define the interactions with the user (and their possible consequences) we will employ events, that is instances of realizations of the abstract event class, defined below.


  abstract class event { 
event
pubic void dependent(event e) { ... } pubic void process() { ... } public void operator(); // abstract method private event[] dep; };
Since a simple event (for example, the modification of a value) may result in a series of events (needed to keep the system in a consistent state), an event object maintains a set of dependent events, which may be activated using the process method. Further, each class derived from event is assumed to define the application operator, that is the actual actions resulting from activating the event. The first realization of the abstract event class is the update event class, which corresponds to retrieving a new temperature value from the user.


  class update extends event { 
update
public update(thermometer th, prompter p) { _th =th; _p = p; } void operator()() { _th.set( _p.get() ); process(); } thermometer _th; prompter _p; };
An update involves a thermometer and a prompter, which are stored when creating the update event object. Activating an update event instance results in retrieving a value from the prompter, setting the thermometer to this value and activating the dependent events. In a similar way, we define the second realization of the abstract event class, the show event class, which corresponds to displaying the value of a thermometer.


  class show extends event { 
show
public show(thermometer th, displayer d) { _th = th; _d = d; } public void operator() { _d.put( _th.get() ); process(); } thermometer _th; displayer _d; };
Activating a show event instance results in retrieving a value from the thermometer, putting that value on display and activating the events associated with this event.

The installation

The next step we must take is to install the application, that is to create the objects comprising the functional behavior of the system, the user interface objects and (finally) the various event objects.


  thermometer c = new centigrade();
  thermometer f = new fahrenheit();
  
  displayer cd = new displayer("centigrade");
  displayer fd = new displayer("fahrenheit");
  
  prompter cp = new prompter("enter centigrade value");
  prompter fp = new prompter("enter fahrenheit value");
  
  show sc = new show(c,cd);
  show sf = new show(f,fd);
  
  update uc = new update(c,cp);
  update uf = new update(f,fp);
  
Having created the objects, we are almost done. The most important and perhaps difficult part is to define the appropriate dependencies between the respective event objects.


  uc.dependent(sc);
  uc.dependent(sf);
  uf.dependent(sc);
  uf.dependent(sf);
  
As shown above, we declare the event of showing the value of the centigrade thermometer (and also of the fahrenheit thermometer) to be dependent upon the event of updating the value of the centigrade thermometer. And we repeat this declaration for the event of updating the value of the fahrenheit thermometer. We may now allow the user the choice between updating the centigrade or fahrenheit thermometer temperature value, for example by inserting these events in a menu, as indicated below


  menu.insert(uc);
  menu.insert(uf);
  
The reader is urged to do some mental processing to check that updating the value of one thermometer actually results in changing the value displayed for the other thermometer as well.

Discussion

Organizing interactions with the user (and the other components of the system as well) by means of events provides a powerful way in which to control the consequences of one particular (kind of) interaction. The advantage of such an approach is that the repertoire of possible interactions can easily be extended or modified without affecting the other parts of the system (the parts realizing the functional behavior of the system and the particularities of the user interface). From the perspective of design, it is a good alternative for defining {\em behavioral compositions} (and its corresponding protocol of interaction) in a more or less formal way. See also section Cooperation.

(C) Æliens 04/09/2009

You may not copy or print any of this material without explicit permission of the author or the publisher. In case of other copyright issues, contact the author.