Sign in to follow this  

Alternative to Singleton/Global for Event Handler

This topic is 3947 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I have two major classes that I make heavy use of in my code: EventManager and EventListener (an interface). Classes which implement EventListener can register themselves with the EventManager, which they can then use to listen to specific events. Any subsystem can post events to the EventManager. Right now my EventManager is a Singleton, which I would like to get away from. It made sense to me for the following reasons: (1)There's only ever going to be one 'master' EventManager. (2)The code in the EventManager is relatively fixed; that is, I should never need to inherit from it. (3)A lot of my classes need to post events to the EventManager. Passing an EventManager around as a parameter to, say, constructors of these classes quickly becomes very cumbersome (and sometimes introduces illogical coupling - when a child in a heirarchy needs an instance and the parent doesn't, the parent still has to take it to pass it down to the child). But still, the Singleton approach introduces global nastiness and makes for some unclear dependencies. So I'm looking for insights into a cleaner design. There's a lot of talk around here about why Singletons suck, but I'd like to get ideas for solutions to the problems they create. :-) The only solution I've thought of so far is to introduce an "EventManagerFactory" with a method "CreateEventManager" that simply returns the same EventManager each time it's called, but that seems a little... hackish to me. Cheers, --Brian

Share this post


Link to post
Share on other sites
Quote:
Original post by Nairb
Right now my EventManager is a Singleton, which I would like to get away from. It made sense to me for the following reasons:
(1)There's only ever going to be one 'master' EventManager.
(2)The code in the EventManager is relatively fixed; that is, I should never need to inherit from it.
(3)A lot of my classes need to post events to the EventManager. Passing an EventManager around as a parameter to, say, constructors of these classes quickly becomes very cumbersome (and sometimes introduces illogical coupling - when a child in a heirarchy needs an instance and the parent doesn't, the parent still has to take it to pass it down to the child).

But still, the Singleton approach introduces global nastiness and makes for some unclear dependencies.


Singletons don't suck. They have their place. They also tend to get highly abused.

In your case, with your requirements, there seems to be no need for more de-coupled design.

Events fall under general communication concepts. As such, they need to be globally accessible. Handling of events is what needs to be decoupled, and that seems to be done.

If you find youself with many different managers, then something like slot system works. You have a global slot manager, and when an objects needs to either produce or receive data, it obtains a reference to apropriate object from this global instance.

Coupling event system too tightly into your code defeats the purpose of de-coupling - you might as well just pass the actual references of handlers.

Share this post


Link to post
Share on other sites
An EventManagerFactory which returns the same instance every time is still a singleton.

So, you're looking at passing a communications channel to your classes. The first step is understanding why a child class needs a channel while the parent class doesn't — this is a design inconsistency which should be solved in two ways:

  • It makes sense for the parent class to require a channel. In that case, reformulate the design so that the channel is a constructor argument to the parent class.
  • It doesn't make sense for the parent class to require a channel. In that case, you should probably split the child class in two, let the parent handle the channel-independent part, and put the channel-dependent part somewhere else.


Can you give a few examples of parents which don't need the channel, but children which need it?

Share this post


Link to post
Share on other sites
Quote:

Any subsystem can post events to the EventManager.


As long as this is a design requirement, you're going to end up with a global/singleton/static hack. I don't know what you often use it for, or how complicated the fix would be but my first instinct would be to localize the EventManager to the subsystem. Each piece of a subsystem could then listen to its local Manager.

If you still then need some sort of globalish events, the subsystems could listen for a small subset in a EventManager in your game/application class. You'll still kinda have a global but you cut down on the code it's visible to greatly.

Share this post


Link to post
Share on other sites
Why have an EventManager class at all? Wouldn't it make more sense to have individual Events which are handled separately?

Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
Why have an EventManager class at all? Wouldn't it make more sense to have individual Events which are handled separately?


It's probably to help with the decoupling. A system can say "post a PLAY_SOUND message" without having to know about who consumes the event. It sounds like the event manager is what keeps track of what listeners listen to what events.

As for the OP, it might be useful to decouple the event manager from its interface. That way, anything that needs to publish events just talks to the interface (which can be as simple as a couple of functions or static methods), and you don't need to pass in a reference to the actual manager (which does have a bit of a painful feel to it).

Share this post


Link to post
Share on other sites
Quote:
Original post by Replicon
It's probably to help with the decoupling. A system can say "post a PLAY_SOUND message" without having to know about who consumes the event. It sounds like the event manager is what keeps track of what listeners listen to what events.

Erm, I don't think you took my meaning. If there's going to be a PLAY_SOUND message which "someone" posts and "someone" listens to, why not have Event playSoundEvent; which stands on its own rather than being in a std::map somewhere in the bowels of EventManager?

Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
Quote:
Original post by Replicon
It's probably to help with the decoupling. A system can say "post a PLAY_SOUND message" without having to know about who consumes the event. It sounds like the event manager is what keeps track of what listeners listen to what events.

Erm, I don't think you took my meaning. If there's going to be a PLAY_SOUND message which "someone" posts and "someone" listens to, why not have Event playSoundEvent; which stands on its own rather than being in a std::map somewhere in the bowels of EventManager?


True, but I was thinking a bit more of a generic case, where any number of listeners can listen for any combination of events, and be managed dynamically (stop listening to some, etc.) Then, there is going to be a many-to-many mapping that needs to be represented in the system somehow. So when something creates Event playSoundEvent; and sends it off into the ether, it needs to know how to find its way to the x listeners. How does your solution achieve that? I'm not trying to instigate, just I come from a different sector of the software industry, and am hoping to learn a new technique :D.

Share this post


Link to post
Share on other sites
Quote:
Original post by Replicon
True, but I was thinking a bit more of a generic case, where any number of listeners can listen for any combination of events, and be managed dynamically (stop listening to some, etc.) Then, there is going to be a many-to-many mapping that needs to be represented in the system somehow. So when something creates Event playSoundEvent; and sends it off into the ether, it needs to know how to find its way to the x listeners. How does your solution achieve that?

With no problem at all. If a listener decides it wants to quit listening to an event, it simply unregisters itself from that event. If it wants to register itself with multiple events, it calls register on those events. There's really very little difference in functionality; basically the only difference is that instead of calling eventManager.registerForSpecificEvent("SOME_EVENT_UNIQUE_IDENTIFIER", this) you call someEvent.register(this). Different events are different Things; they shouldn't pretend they're all bits of a larger object.

Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
With no problem at all. If a listener decides it wants to quit listening to an event, it simply unregisters itself from that event. If it wants to register itself with multiple events, it calls register on those events. There's really very little difference in functionality; basically the only difference is that instead of calling eventManager.registerForSpecificEvent("SOME_EVENT_UNIQUE_IDENTIFIER", this) you call someEvent.register(this). Different events are different Things; they shouldn't pretend they're all bits of a larger object.


Ah, I see, so really, your Event playSoundEvent; is really more of a global-scoped thing than one whose lifetime is the sending of one message.

Share this post


Link to post
Share on other sites
Exactly. Particular event invocations wouldn't necessarily have object type; that depends on other factors. The events don't have to be global-scoped, though: a particular event just needs to be placed such that the senders and receivers can find it. In particular, it doesn't need to be available to every sender and every receiver of every event in the entire system.

Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
With no problem at all. If a listener decides it wants to quit listening to an event, it simply unregisters itself from that event. If it wants to register itself with multiple events, it calls register on those events. There's really very little difference in functionality; basically the only difference is that instead of calling eventManager.registerForSpecificEvent("SOME_EVENT_UNIQUE_IDENTIFIER", this) you call someEvent.register(this). Different events are different Things; they shouldn't pretend they're all bits of a larger object.


Do you mean using a static method for registering event listeners? The "event-manager problem" has always plagued me, and I want to make sure I know exactly what you mean.

- Rob

Share this post


Link to post
Share on other sites
Nope. Following the somewhat fictitious naming I adopted earlier in the thread, the Event class has a non-static registration function. Each event (say, a particular Enemy's onDie event, or the Game's onGameOver event) will have listeners separately registered.

Actually what I use is boost::signals. But this is pretty much how it works.

Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
Nope. Following the somewhat fictitious naming I adopted earlier in the thread, the Event class has a non-static registration function. Each event (say, a particular Enemy's onDie event, or the Game's onGameOver event) will have listeners separately registered.

Actually what I use is boost::signals. But this is pretty much how it works.


So the onDie event (frex) is an instance member? It must be non-private -- otherwise, other objects wouldn't be able to access it. Doesn't this violate encapsulation?

- Rob

Share this post


Link to post
Share on other sites
Well, sort of. But you can live with that. If you really think someone resets your event, return a reference trough a method.

options based on this event type:


class Event {
public void Register(...);
public void Call(...);
}



class Mouse {
private Event onLeftButtonDown;
public Event& OnLeftButtonDown() { return onLeftButtonDown; }
}


or even

class Mouse {
private Event onLeftButtonDown;
public void RegisterOnLeftButtonDown(...) { onLeftButtonDown.Register(...); }
public void RaiseOnLeftButtonDown(...) { onLeftButtonDown.Call(...); }
}


this second one allows proper usage of private, protected and public on each part, if needed...


I've implemented once a similar scheme (though, I just made them all public fields...), so in essence copied boost::signal on my own. Nowadays, I code in c# mostly, and there, the event mechanism is about equal. It's a great system to decouple and late couple and recouple your components, quite cool... (and in c#, you can even do it in the editor with your forms, and there the real fun starts :)).

No need for some EventManager.

Share this post


Link to post
Share on other sites
Quote:
Original post by RobAU78
So the onDie event (frex) is an instance member? It must be non-private -- otherwise, other objects wouldn't be able to access it. Doesn't this violate encapsulation?

Yes, it is an instance member, and yes it is public. And my opinion is that it doesn't violate encapsulation. Signals, like functions, are part of a class' public interface. The fact that another object could screw around (or call) the event is something to think about, but hardly a deal-breaker. (Side note: IMHO, member signals should always be mutable. But that's a debate for another day.)

Share this post


Link to post
Share on other sites
Quote:
Original post by davepermen
It's a great system to decouple and late couple and recouple your components, quite cool...

Boy, howdy. I find that signals quite neatly fill the gaps in good OO design and implementation, especially in languages like C++ which discourage the use of listeners.

Share this post


Link to post
Share on other sites
Agreed (and written above). In the end, in c++, you end up coding something like boost signals anyways.

I just never liked the terminology of it. But that's a small issue. (C++ always has .. special names for ordinary things anyways, sometimes i like that, sometimes not..)

Share this post


Link to post
Share on other sites
Sneftel and Dave Permen, thanks for your replies. I have a much better understanding of what you both are talking about.

You're right in that signals (and slots) are more like functions and function pointers than full-fledged objects. As a result, they can indeed be treated as part of an object's interface -- and they should be. Doing so obviates the need for any sort of "event manager".

For a language like C++, I like how this paradigm provides a sort of pluggable interface between objects. What I'm wondering is whether anyone has made signal/slot templates -- i.e., only certain sets of signals and slots can connect to each other. Here I'm not talking about type-safety, which is already handled by the Boost.Signals library.

- Rob

Share this post


Link to post
Share on other sites
Hmmm. That depends on what you mean by "templates". Are you trying to ensure that a slot which takes (double length, double width) doesn't get connected to a signal which sends (double mean, double variance)? Or are you talking about making sure that a set of ElevatorMovedUp and ElevatorMovedDown listeners are concurrently attached?

Share this post


Link to post
Share on other sites
Can't do it. Period. The listener can do whatever he wants with the passed arguments; there's nothing to stop him from treating them in semantically meaningless ways. Of course, the parameters to a signal/event should all be named (in code if possible, otherwise in comments) and documented, so a user who does so only has himself to blame.

Share this post


Link to post
Share on other sites
Crap. Why did I say "former"? I meant "latter". Seriously.

Basically I'm talking about associating two complementary sets of signals and slots -- an OOP version of a physical plug-and-socket.

- Rob

Share this post


Link to post
Share on other sites
That would be an interface. [smile] As I said before, C++ discourages you from using proper interface-typed listeners, but you can do it, and it's the only elegant way I can think of to accomplish that sort of thing.

Share this post


Link to post
Share on other sites

This topic is 3947 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this