Event-driven control

How to model complex interactions is one of the (perhaps most important) issues that needs to be resolved to arrive at a (proper) discipline of object-oriented program design. In section systems we looked at the notions of abstract systems (representing the combined interface of the object classes involved) and events (which are used to organize the interactions between the various objects living in the system). To conclude this chapter about composition mechanisms, we will look at a somewhat more detailed example employing (user-defined) events to characterize and control the interaction between the objects representing the functional aspects of the application and the objects comprising the user interface (allowing the user to interact with the system). The example is taken from  [Henderson93]. Before describing the actual classes, we will first give an overview of the structure of the application. Our example involves the use of thermometers. The abstract system (see section systems) characterizing the functionality of our application may be characterized as in slide 7-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 form 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. See slide 7-as-therm.
  class thermometer { 
\fbox{thermometer}
public: virtual void set(float v) { temp = v; } virtual float get() { return temp; } protected: float temp; thermometer( float v ) : temp(v) { } };

slide: The thermometer class

Since only derived classes can use the protected constructor, no direct instances of thermometer exist, so the class is abstract. Moreover, both the member functions set and get are defined as virtual. We will distinguish between two kinds of thermometers, measuring temperatures respectively in centigrade and fahrenheit. See slides sli-7-as-cent and sli-7-as-fahr.
  class centigrade : public thermometer { 
\c{\fbox{centigrade}}
public: centigrade() : thermometer(0) { } void set(float v) { temp = v + 273; } float get() { return temp - 273; } };

slide: The centigrade class

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 : public thermometer { 
\c{\fbox{fahrenheit}}
public: fahrenheit() : thermometer(0) { } void set(float v) { temp = (v - 32) * 5/9 + 273; } float get() { return temp * 9/5 + 32 - 273; } };

slide: The fahrenheit class

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 as shown in slide 7-as-displayer.
  class displayer : public window { 
\c{\fbox{displayer}}
public: displayer(); void put(char* s); void put(float f); };

slide: The displayer class

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). See slide 7-as-prompter.
  class prompter : public window { 
\c{\fbox{prompter}}
public: prompter(char* text); float get(); char* gets(); };

slide: The prompter class

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 in slide 7-as-event.

Events

-- to define interactions
  class event { 
\fbox{\fbox{event}}
public: void dependent(event* e); void process(); virtual void operator()() = 0; private: set* dep; };

slide: The event class

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 function event::process. 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. See slide 7-as-update.
  class update : public event { 
\fbox{update}
public: update(thermometer* th, prompter* p) : _th(th), _p(p) {} void operator()() { _th->set( _p->get() ); event::process(); } protected: thermometer* _th; prompter* _p; };

slide: The update class

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. See slide 7-as-show.
  class show : public event { 
\fbox{show}
public: show(thermometer* th, displayer* d) : _th(th), -d(d) {} void operator()() { _d->put( _th->get() ); event::process(); } protected: thermometer* _th; displayer* _d; };

slide: The show class

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. See slide 7-as-install.
  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);
  

slide: Installing the objects

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. See slide 7-as-depend.

Dependencies

-- to interconnect events
  uc->dependent(sc);
  uc->dependent(sf);
  uf->dependent(sc);
  uf->dependent(sf);
  

slide: Assigning dependencies

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 (in my view) the best approximation we have of defining {\em behavioral compositions} (and its corresponding protocol of interaction) in a formal way. See also section formal-coop.