|
On the Difficulties of Event Handlers in Object Oriented
Languages
Maintained by: David Austin
|
When developing a modular, large scale system such as the software
needed to control a robot, one often needs to respond to events.
Events can come from a variety of sources, for example the receipt of
a message from another module is a type of (very frequent) event but
also events come from external sources such as the user pressing the
emergency stop. While the lower levels of control of a robot are
performed at a fixed frequency, there is still a need to respond to
events at this low level and the higher levels of control are
fundamentally based on the low frequency occurence of events.
In an object-oriented language, the problem can be stated as follows:
What methodology should be used to notify objects of the occurence of
events? This document discusses methods for doing this and some of the
non-obvious shortcomings of some of the methods that the author has
experienced.
The obvious way to notify an object of an event is to call a member
function. For example, one could do the following:
class NotifyObjectBase {
public:
NotifyObjectBase();
virtual void HandleEvent(EventType event) = 0;
};
class Object1 : public NotifyObjectBase {
public:
Object1();
void HandleEvent(EventType event) {
if (event.type == 0)
cout << "Event 0 happened\n";
}
};
|
Ok, this looks great and seems to exemplify the virtues of object
oriented programming. Whenever a class needs to be notified of an
event, we simply call the HandleEvent member function.
All classes that need to detect events can be derived from the
NotifyObjectBase base class.
However, all is not fine. Introduction of a new class illustates the
problems:
class NotifyObjectBase {
public:
NotifyObjectBase();
virtual void HandleEvent(EventType event) = 0;
};
class Object1 : public NotifyObjectBase {
public:
Object1();
void HandleEvent(EventType event) {
if (event.type == 0)
cout << "Event 0 happened\n";
}
};
class Object2 : public Object1 {
public:
Object2();
void HandleEvent(EventType event) {
if (event.type == 1)
cout << "Event 1 happened\n";
}
};
|
The programmers of Object1 and Object2 would
probably expect that their handler would be called for all events.
Instead, the implementation of Object2 has in some sense
changed the implementation of Object1. Normally, we wish
to change the definition of a member function when we overload it, but
not in this case. Here overloading produces non-obvious results
and/or introduces nasty dependencies between objects.
One way to work around this problem is to call parent member
functions.
2.1 Calling Parent Member Functions
To avoid the problem of overloading of member functions completely
removing the previous behaviour, one can try calling the parent
function if the event is not handled at the current level. For
example:
class NotifyObjectBase {
public:
NotifyObjectBase();
virtual int HandleEvent(EventType event) {
return 0;
};
};
class Object1 : public NotifyObjectBase {
public:
Object1();
int HandleEvent(EventType event) {
if (event.type == 0) {
cout << "Event 0 happened\n";
return 1;
}
return NotifyObjectBase::HandleEvent(event);
}
};
class Object2 : public Object1 {
public:
Object2();
int HandleEvent(EventType event) {
if (event.type == 1) {
cout << "Event 1 happened\n";
return 1;
}
return Object1::HandleEvent(event);
}
};
|
While this methodology for event handling works, it has become a bit
messy with a extra dependency on the parents object(s). Also, the case of
multiple inheritance gets really messy. For example:
class NotifyObjectBase {
public:
NotifyObjectBase();
virtual int HandleEvent(EventType event) {
return 0;
};
};
class Object1 : public NotifyObjectBase {
public:
Object1();
int HandleEvent(EventType event) {
if (event.type == 0) {
cout << "Event 0 happened\n";
return 1;
}
return NotifyObjectBase::HandleEvent(event);
}
};
class Object2 : public Object1 {
public:
Object2();
int HandleEvent(EventType event) {
if (event.type == 1) {
cout << "Event 1 happened\n";
return 1;
}
return Object1::HandleEvent(event);
}
};
class Object3 : public Object1 {
public:
Object3();
int HandleEvent(EventType event) {
if (event.type == 2) {
cout << "Event 2 happened\n";
return 1;
}
return Object1::HandleEvent(event);
}
};
class Object4 : public Object2, Object3 {
public:
Object4();
int HandleEvent(EventType event) {
if (event.type == 3) {
cout << "Event 3 happened\n";
return 1;
}
return Object2::HandleEvent(event) || Object3::HandleEvent(event);
}
};
|
Now the event handler for Object1 can be called twice for
the same event! One could add flags to fix this but the whole
structure is starting to become an uncontrollable mess and certainly
has none of the initial attraction of the first example shown above.
An alternative to member function notification is to use static
functions. In general, it is not possible to obtain pointers to
member functions and to call these later (for example, C++ does not
permit this). However, for static functions (or non-member
functions), it is possible to obtain a pointer and to call the
function using the pointer. The down-side of this approach is that in
both a static function and a non-member function, one cannot use the
this pointer to the object or the member variables and
functions of the object. However, it is possible to access the public
variables and member functions through a pointer or reference to the
object and, in a static member function, one can access the private
variables and member functions as well.
Need a code example here.
The first conclusion is that it's not as easy as it seems! My
decision has been to adopt the static function approach even though
this will probably cause object-oriented programming purists
considerable nausea. My justification is that the static function
approach is the most general and can simply be used to implement the
member function approach as follows:
class NotifyObjectBase {
public:
NotifyObjectBase();
virtual int HandleEvent(EventType event) {
return 1;
}
static int EventHandlerFunction(EventType event, NotifyObjectBase *that) {
return that->HandleEvent(event);
}
};
|
|
Copyright 2001 David Austin
|