Subscriber callback interface design

A

Ash

Hello all,
I am hoping this is the appropriate newsgroup for a C++ interface
design question. I am trying to design an interface for a subscriber
to register/deregister handlers for various events. The callbacks
specified by the subscriber will be called when the events get
trigerred in a different thread. Each event has different kinds of
data associated with it. To achieve this I have the following:

// The following describes the event types
enum EventTypeEnum
{
EVENT_TYPEA.
EVENT_TYPEB,
EVENT_TYPEC,
EVENT_TYPE_LAST
};

// The following defines the event objects of each type
class EventTypeObj
{
public:
EventTypeObj (EventTypeEnum evtType);
~EventTypeObj ();

// additional methods to get/set various attributes on event objects
};

// On event triggering the following kinds of data will be passed in
// to the handler
class EventTypeAData;
clsss EventTypeBData;
class EventTypeCData;


// The following interface needs to be implemented by each subscriber
// to handle events
class EventHandlerInterface
{
public:
virtual void HandleEventA (EventTypeAData info) = 0;
virtual void HandleEventB (EventTypeBData info) = 0;
virtual void HandleEventC (EventTypeBData info) = 0;
};


So, the subscriber can sub-class the event handler interface to
implement their own logic of handling the events. Now, how the caller
can subscribe to the events:


// Object encapsulates the registration and de-registration of event
class EventRegistry
{
private:
// ctor and dtor are private because a single instance
// exists in every process
EventRegistry ();
~EventRegistry ();

// To register for an event, specify the type of the event
// and the object that is handling the event
// May be the evnet handler interface object should be recounted
// so that if the subscriber deletes the object we don't deliver
the event
void RegisterEvent (EventTypeEnum evtType,
EventHandlerInterface *hndlrObj);

void DeRegisterEvent (EventTypeEnum evtType,
EventHandlerInterface *hndlrObj);

};

The good thing about the above subscriber interface is that all the
event handlers are encapsulated in a single object. The bad thing is
if we add new events everyone has to recompile but since this is only
for internal use so it is ok for now. I could have provided separate C
method signatures for each type of event but I prefer providing a
handler interface. Also, I tried using functors to encapsulate the
events but I wasn't sure how to and so I didn't. What do people think
about the above interface? Any thoughts/suggestions would be
appreciated.

Regards,
Ash
 
N

Nobody

My comments are inlined below...

Ash said:
Hello all,
I am hoping this is the appropriate newsgroup for a C++ interface
design question. I am trying to design an interface for a subscriber
to register/deregister handlers for various events. The callbacks
specified by the subscriber will be called when the events get
trigerred in a different thread. Each event has different kinds of
data associated with it. To achieve this I have the following:

// The following describes the event types
enum EventTypeEnum
{
EVENT_TYPEA.
EVENT_TYPEB,
EVENT_TYPEC,
EVENT_TYPE_LAST
};

Just a pet peeve of mine that probably no one on this group will agree with,
but I *HATE* enums... yes they do provide some sanity checking, but they
tend to cause problems if you ever use COM/DCOM. They are also a hassle to
debug sometimes because you aren't sure what values they are assigned...
yes, you can hardcode values, but then why not just use a #define? I prefer
to use #defines and define flags.
// The following defines the event objects of each type
class EventTypeObj
{
public:
EventTypeObj (EventTypeEnum evtType);
~EventTypeObj ();

// additional methods to get/set various attributes on event objects
};

// On event triggering the following kinds of data will be passed in
// to the handler
class EventTypeAData;
clsss EventTypeBData;
class EventTypeCData;

If its data put it in a struct not a class... yes, I know, technically same
thing... but I use structs for data only and classes for data/methods. I
hate people who put constructors in a structure... what if I want to use C?
// The following interface needs to be implemented by each subscriber
// to handle events
class EventHandlerInterface
{
public:
virtual void HandleEventA (EventTypeAData info) = 0;
virtual void HandleEventB (EventTypeBData info) = 0;
virtual void HandleEventC (EventTypeBData info) = 0;
};

Why?

This is a VERY poor design. What if I only want to subscribe to EventA? I
would need to at least stub out the other two.. or what if I only want B &
C? same issue, I need to stub out A.

I would make it a C callback function. Surely EventTypeA,B and C have
something in common right? Why not make a "base" structure and inherit the
types from that... then have a SINGLE callback function that can handle all
three types using polymorphism?
So, the subscriber can sub-class the event handler interface to
implement their own logic of handling the events. Now, how the caller
can subscribe to the events:


// Object encapsulates the registration and de-registration of event
class EventRegistry
{
private:
// ctor and dtor are private because a single instance
// exists in every process
EventRegistry ();
~EventRegistry ();

// To register for an event, specify the type of the event
// and the object that is handling the event
// May be the evnet handler interface object should be recounted
// so that if the subscriber deletes the object we don't deliver
the event
void RegisterEvent (EventTypeEnum evtType,
EventHandlerInterface *hndlrObj);

void DeRegisterEvent (EventTypeEnum evtType,
EventHandlerInterface *hndlrObj);

};

"DeRegister" is not a word, but "Unregister" is...
The good thing about the above subscriber interface is that all the
event handlers are encapsulated in a single object. The bad thing is
if we add new events everyone has to recompile but since this is only
for internal use so it is ok for now. I could have provided separate C
method signatures for each type of event but I prefer providing a
handler interface. Also, I tried using functors to encapsulate the
events but I wasn't sure how to and so I didn't. What do people think
about the above interface? Any thoughts/suggestions would be
appreciated.

Forcing everyone to recompile because you add a new type would have me send
you back to the drawing board if I was reviewing the design... What if you
ever have 3rd party developers writing modules for this? you are going to
force all them to recompile too? When microsoft releases a new version of
Windows, do they force you to recompile? nope... well only if you want to
use the new features...

binary backwards compatibility is your friend...

I have a co-worker who CONTINOUSLY sticks stuff in the middle of existing
structures and breaks binary compatibility. I have told him a million times
that he is breaking everything by doing that. He never listens... maybe when
I hit him with my car the next time he does that he'll start listening :)
His excuse is we always rebuild everything anyways.. Well, thats going to
get him in trouble when we launch our per module upgrade capability... let
him come in on the weekends to fix it :)

Anyways, if you use the single callback function with polymorphism and a
type as part of the data then this problem is solved.

Oh, and PS... kindly add a void* type so I can have user defined data passed
back to my callback :)
 
I

Ian

Nobody said:
My comments are inlined below...




Just a pet peeve of mine that probably no one on this group will agree with,
but I *HATE* enums... yes they do provide some sanity checking, but they
tend to cause problems if you ever use COM/DCOM. They are also a hassle to
debug sometimes because you aren't sure what values they are assigned...
yes, you can hardcode values, but then why not just use a #define? I prefer
to use #defines and define flags.
?

If you are using enumerations, why would you want to know the (numeric?)
value assigned. The enum is the value - any debugger worth using will
display it as such.

Compile time type safety is one of the big benefits of C++, don't throw
it away for no good reason.

Ian

<snip>
 
D

Denis Remezov

Nobody said:
My comments are inlined below...



Just a pet peeve of mine that probably no one on this group will agree with,
but I *HATE* enums...

I hate them too, yet use them. It's getting better. Sometimes const int
(or #define, now mainly for legacy reasons) seem to fit better still.
yes they do provide some sanity checking, but they
tend to cause problems if you ever use COM/DCOM. They are also a hassle to
debug sometimes because you aren't sure what values they are assigned...
yes, you can hardcode values, but then why not just use a #define? I prefer
to use #defines and define flags.


If its data put it in a struct not a class... yes, I know, technically same
thing... but I use structs for data only and classes for data/methods. I
hate people who put constructors in a structure... what if I want to use C?


Why?

This is a VERY poor design. What if I only want to subscribe to EventA? I
would need to at least stub out the other two.. or what if I only want B &
C? same issue, I need to stub out A.

I would make it a C callback function. Surely EventTypeA,B and C have
something in common right? Why not make a "base" structure and inherit the
types from that... then have a SINGLE callback function that can handle all
three types using polymorphism?

All good points. Consider, however, a special case:
a) A typical event handler is interested in all (or nearly all) events, and
b) The set of events is more or less fixed by design (we may think about a
hypothetical possibility of adding new event types, but, let's say, it's
just highly unlikely).

In this case, to avoid overdesigning, I would probably keep the original
EventHandlerInterface and change RegisterEvent/UnregisterEvent to accept
just a pointer to the interface without the event type (all HandleEvent__s()
will be called).
Compared to your more general solution, this design is quite simpler:
- an implementation of EventHandlerInterface does not need to do
dispatching (a big relief);
- EventHandlerInterface doesn't care about EventTypeEnum;
- event data types do not need to have a single base class (no, they may
happen to have nothing in common);
- registration is much easier;
- there is less coding.

If the set of events does change then we have two options:
0) Update EventHandlerInterface and break the binary compatibility;
1) Create a new EventHandlerInterface2.
Shouldn't ever need them, but things happen.

As I said, this was a somewhat specific example, but it occurs all the
time nevertheless. I thought I'd just throw it in, without knowing the
details of the OP's problem.

Denis
 
A

ash

My comments are inlined below...
My comments are inlined below...




Just a pet peeve of mine that probably no one on this group will agree with,
but I *HATE* enums... yes they do provide some sanity checking, but they
tend to cause problems if you ever use COM/DCOM. They are also a hassle to
debug sometimes because you aren't sure what values they are assigned...
yes, you can hardcode values, but then why not just use a #define? I prefer
to use #defines and define flags.




If its data put it in a struct not a class... yes, I know, technically same
thing... but I use structs for data only and classes for data/methods. I
hate people who put constructors in a structure... what if I want to use C?




Why?

This is a VERY poor design. What if I only want to subscribe to EventA? I
would need to at least stub out the other two.. or what if I only want B &
C? same issue, I need to stub out A.

I would make it a C callback function. Surely EventTypeA,B and C have
something in common right? Why not make a "base" structure and inherit the
types from that... then have a SINGLE callback function that can handle all
three types using polymorphism?

I am not sure I understand what you mean by creating a base structure?
How about an example to illustrate the point?

Looking over your other comments, what do you think about a base
structure like the following:

class BaseHandler
{
public:
virtual void HandleEvent (const EventTypeObj evtType,
EventTypeDataBase *pData) = 0;
};

That way anyone wanting to subscribe to the events A, B and C can
sub-class from the BaseHandler and pass the appropriate instance of the
sub-classed object to the event registration. Since we pass in an
EventTypeObj the subscriber can query for properties/type of the event
in the HandleEvent method.

The additional benefit of this is no need of recompilation of previous
subscribers as new event types are added.

The thing I don't like about the above is that it does not give any
type safety. The handler has to do a dynamic cast of the pData to the
the appropriate type (EventTypeAData, EventTypeBData ...). But I guess
doing a dynamic cast provides some type checking.
Oh, and PS... kindly add a void* type so I can have user defined data passed
back to my callback :)

The whole idea of having the handler be a C++ object was so that the
handler object maintains its own state and if the user wants to pass in
a void* pointer then they maintain it inside the appropriately
constructed handler object. So, I am not sure i agree with passing in a
void* to the handler method.

Thanks for the suggestions.

Regards,
Ash
 
D

David Rubin

Denis Remezov wrote:

[snip]
In this case, to avoid overdesigning, I would probably keep the original
EventHandlerInterface and change RegisterEvent/UnregisterEvent to accept
just a pointer to the interface without the event type (all HandleEvent__s()
will be called).
Compared to your more general solution, this design is quite simpler:
- an implementation of EventHandlerInterface does not need to do
dispatching (a big relief);

This is a very important point. In general, clients should be able to
register handlers on a per-event basis. This goes a long way towards
insulating clients from changes to the Event interface.
- EventHandlerInterface doesn't care about EventTypeEnum;

And It shouldn't be as event handlers are only called for the types they
are registered for (and parameterized by event-specific data types if
necessary).
- event data types do not need to have a single base class (no, they may
happen to have nothing in common);
- registration is much easier;
- there is less coding.

One thing you might want to consider is allowing clients to register
event handler functors rather than event handler objects. This allows
you to 1) use free functions as an event handler while at the same time
2) use a (pointer-to) member function, but avoid multiple inheritance
(of MyBase + EventHandlerInterface) in your class hierarchy.

/david
 
E

Edward Diener

Ash said:
Hello all,
I am hoping this is the appropriate newsgroup for a C++ interface
design question. I am trying to design an interface for a subscriber
to register/deregister handlers for various events. The callbacks
specified by the subscriber will be called when the events get
trigerred in a different thread. Each event has different kinds of
data associated with it. To achieve this I have the following: snip...

Have you looked at the Boost Signals library by Douglas Gregor at
http://www.boost.org . It is a generic interface for C++ event handling
which is much more flexible than the one you are creating for yourself. It
should be very easy for you to use with your own particular functionality.
 
N

Nobody

Again, comments are inlined :)

ash said:
My comments are inlined below...


I am not sure I understand what you mean by creating a base structure?
How about an example to illustrate the point?

For example, if I have a callback function it would be like:

typedef struct
{
enum dataType;
int common1;
int common2;
} CBDATA;

typedef struct
{
CBDATA m_hdr;
int data_a1;
int data_a2;
} CBDATA_A;

long CallbackFunc(CBDATA* pData)
{
switch (pData->dataType)
{
case A: Work((CBDATA_A*)pData);
case B: Work((CBDATA_B*)pData);
}
}

This method doesn't provide tons of type safety since you can cast to the
wrong structure type, but that would quickly be discovered :). And besides,
you can pretty much cast anything to anything and crash anyways. But this
provides a single callback that can just contain a switch and cast the
structs.
Looking over your other comments, what do you think about a base
structure like the following:

class BaseHandler
{
public:
virtual void HandleEvent (const EventTypeObj evtType,
EventTypeDataBase *pData) = 0;
};

That way anyone wanting to subscribe to the events A, B and C can
sub-class from the BaseHandler and pass the appropriate instance of the
sub-classed object to the event registration. Since we pass in an
EventTypeObj the subscriber can query for properties/type of the event
in the HandleEvent method.

The additional benefit of this is no need of recompilation of previous
subscribers as new event types are added.

The thing I don't like about the above is that it does not give any
type safety. The handler has to do a dynamic cast of the pData to the
the appropriate type (EventTypeAData, EventTypeBData ...). But I guess
doing a dynamic cast provides some type checking.

I don't like using C++ for something I am going to use in a event/callback
system because it forces everyone to use C++.
The whole idea of having the handler be a C++ object was so that the
handler object maintains its own state and if the user wants to pass in
a void* pointer then they maintain it inside the appropriately
constructed handler object. So, I am not sure i agree with passing in a
void* to the handler method.

Well, if you do that class method then you don't need it... if you do the
other way you do need it to provide a way for the handler to maintain its
own state.
 
A

Alan Johnson

Ash said:
Hello all,
I am hoping this is the appropriate newsgroup for a C++ interface
design question. I am trying to design an interface for a subscriber
to register/deregister handlers for various events. The callbacks
specified by the subscriber will be called when the events get
trigerred in a different thread. Each event has different kinds of
data associated with it. To achieve this I have the following:

...

The good thing about the above subscriber interface is that all the
event handlers are encapsulated in a single object. The bad thing is
if we add new events everyone has to recompile but since this is only
for internal use so it is ok for now. I could have provided separate C
method signatures for each type of event but I prefer providing a
handler interface. Also, I tried using functors to encapsulate the
events but I wasn't sure how to and so I didn't. What do people think
about the above interface? Any thoughts/suggestions would be
appreciated.

Regards,
Ash

I'm rambling this off the top of my head as I think of it, so feel free
to point out anything that makes no sense. What if, rather than a
single event callback class, you had multiple pure abstract base
classes, one for each event, in fact. This would allow you to create an
object that inherited from each of these that it actually implemented.
Example:

class EventTypeA
{
public :
virtual int callbackA() = 0 ; // could include parameters.
} ;

class EventTypeB
{
public :
virtual int callbackB() = 0 ; // could include parameters.
} ;

class EventTypeC
{
public :
virtual int callbackC() = 0 ; // could include parameters.
} ;

class EventHandler
{
public :
// Returns a pointer to EventTypeX.
virtual void *get_event_type(EventTypeEnum e) = 0 ;
} ;


Now, when you actually implement your handler (forgive me if I get the
syntax wrong, I'm not running through a compiler to check):


// This class handles EventTypeA and EventTypeC, but doesn't want
// notifications for EventTypeB.
class MyHandler : public EventHandler, public EventTypeA, public EventTypeC
{
public :
void *get_event_type(EventTypeEnum e)
{
switch (e)
{
case EVENT_TYPEA :
return reinterpret_cast<EventTypeA *>(this) ;
case EVENT_TYPEC :
return reinterpret_cast<EventTypeC *>(this) ;
default :
return NULL ;
}
}

int callbackA() ;
int callbackB() ;

} ;


Whatever object is doing the actual calling can then check whether an
object wants a particular notification with get_event_type, and call the
particular callback function for the event using the returned pointer.

Unless I've missed something, this should allow you to add events
without everybody needing to recompile. When one of the new events
occurs, and get_event_type is called for it on an old handler, it will
just return NULL. The important thing is that you not change and of the
EventTypeX classes, nor change the value of the EventTypeEnum
representing it.

Please criticize,

Alan
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,049
Latest member
Allen00Reed

Latest Threads

Top