Using One Object Per Event Type With Publish And Subscribe (Pub/Sub)
Yesterday, I was reading an interesting article by Miller Medeiros that compared different types of publish and subscribe mechanisms. Publish and Subscribe (Pub/Sub) is nothing new - if you use jQuery at all, you've probably been using Pub/Sub a lot (even without thinking about it). In the article, however, Miller described a Pub/Sub approach that I had never seen before: Signal. In the signal approach, each event type is defined by an individual event beacon. So, rather than binding to a generic event dispatcher, a subscriber would bind to a specific object that codes for a single event type.
In the more generic, flexible approach to event binding, a subscriber would bind to an event beacon, supplying both an event type and an event callback:
eventedObject.bind( "someEvent", subscriberCallback );
In this case, the subscriber is binding to the event type, "someEvent," and would like the function, "subscriberCallback" to be invoked when that event is dispatched. In the Signal approach, the event binding is performed on an event beacon that codes for a specific event:
eventedObject.someEvent.bind( subscriberCallback );
The main difference here is that in the first approach, "someEvent" is an arbitrary string value; and, in the latter approach, "someEvent" is a property of the evented object.
In theory, both of these approaches are the same - they allow for publishers to expose an event-binding surface; and, they allow subscribers to bind to specific event types. In practice, however, the major difference is that the Signal approach requires a more formalized event architecture; rather than creating just-in-time, string-based event types, each event has to be defined explicitly ahead of time as a property of the evented object before subscribers can even bind to it.
As Miller points out in his pro/cons list, this is probably a good constraint.
Since I had never seen this kind of Pub/Sub architecture before, I thought it would be good to experiment with a little code. And, to make it more fun, I wanted to explore some of the jQuery 1.7 Callbacks information that Addy Osmani discussed in his Demystifying jQuery 1.7's $.Callbacks article. In the following demo, I'm creating a Signal class (which wraps $.Callbacks) and then I'm using it to define several event types on a given object.
<!DOCTYPE html>
<html>
<head>
<title>
Using One Object Per Event Type With Publish And Subscribe
</title>
<script type="text/javascript" src="./jquery-1.7rc1.js"></script>
<script type="text/javascript">
// Define the Signal class. Each signal will be responsible
// for a specific event beacon attached to an object.
function Signal( context, eventType ){
// Store the context (the object on which this event
// is being attached).
this.context = context;
// Store the event type - the name of the event.
this.eventType = eventType;
// Create a queue of callbacks for this event.
this.callbacks = $.Callbacks();
}
// Define the class methods.
Signal.prototype = {
// I bind an event handler.
bind: function( callback ){
// Add this callback to the queue.
this.callbacks.add( callback );
},
// I dispatch the event with the given parameters.
dispatch: function(){
// Invoke the callbacks.
this.callbacks.fireWith(
this.context,
arguments
);
},
// I unbind an event handler.
unbind: function( callback ){
// Remove the callback from the queue.
this.callbacks.remove( callback );
}
};
// -------------------------------------------------- //
// -------------------------------------------------- //
// -------------------------------------------------- //
// -------------------------------------------------- //
// Create an instance of Sarah
var sarah = {};
// Set the name.
sarah.name = "Sarah";
// Create an event collection. In this scenario, each event
// is defined by an actual object - not just an event publish
// and subscribe entry point.
sarah.events = {
wakeUp: new Signal( sarah, "wakeup" ),
shower: new Signal( sarah, "shower" ),
gotoWork: new Signal( sarah, "gotowork" ),
gotoHome: new Signal( sarah, "gotohome" ),
sleep: new Signal( sarah, "sleep" )
};
// Execute one day.
sarah.live = function(){
this.events.wakeUp.dispatch( "Stretch!" );
this.events.shower.dispatch( "Rubba-dub-dub" );
this.events.gotoWork.dispatch( "Hmmph" );
this.events.gotoHome.dispatch( "Yawwwn" );
this.events.shower.dispatch( "Oooooooh" );
this.events.sleep.dispatch( "Sleeeepy!" );
};
// -------------------------------------------------- //
// -------------------------------------------------- //
// Bind to shower events.
sarah.events.shower.bind(
function( metaData ){
// Log event.
console.log( this.name, "[Shower]:", metaData );
}
);
// Execute one day in the life of Sarah.
sarah.live();
</script>
</head>
<body>
<!-- Left intentionally blank. -->
</body>
</html>
As you can see, the Signal class represents a single event type. It then exposes two subscriber methods:
- bind( callback )
- unbind( callback )
This Signal class is then instantiated for each event type available on the "sarah" object. When we bind to events on sarah, we aren't binding to sarah itself; rather, we are binding to the event surface exposed by sarah.
And, when we run the above code, we get the following console output:
Sarah [Shower]: Rubba-dub-dub
Sarah [Shower]: Oooooooh
While this approach does feel a bit awkward, probably due to its novelty, I like the fact that I have to explicitly define my object's event types. I suspect that this would force me to think more holistically about my object architecture. And, I think there would be improved readability and maintainability in being able to see all the events defined in a single place.
Want to use code from this post? Check out the license.
Reader Comments
Nice write-up and video, I hope more people understands the benefits of having a concrete object to each event type and not relying on strings.
I'm not sure if it was by coincidence but [DART DOM event system](www.dartlang.org/articles/improving-the-dom) is very similar to Signals, they use a namespace called "on" to store all the event types instead of "events" tho...
[js-signals](http://millermedeiros.github.com/js-signals/) have a couple extra features like disable/enable a signal, remove all the callbacks at once, ability to set default parameters and change the execution context (`this` object) for each handler. It also doesn't require jQuery and works on nodejs as well.
Cheers.
This pretty much like the signal/slot system in Qt. Classes have signals and slots and you connect signals to slots. When the signal is emited(fired) it calls all the slots connected to it in an arbitrary order.
It must feel somewhat awkward in javascript since the event mechanism is already somewhat native to language unlike in Qt where it was build to make gui easier to make in c++.
[sorry for the double post]
Qt code sample
Yep Guillaume, it's very similar to Qt, AS3-Signals (js-signals was based on AS3-Signals) was based on Qt signals/slots and C# Events.
ActionScript 3 native Events are very similar to DOM2 events (in fact it was based on DOM3 events), and still a lot of AS3 developers started using Signals instead of the native event model (even for native events), the main reason for that is because it doesn't rely on strings (code completion, live error checking / compiler errors, can be added to interfaces, etc..) [more info about it](https://github.com/robertpenner/as3-signals/wiki/).
In JavaScript it makes sense to create a custom pub/sub system since the language doesn't provide an easy way to dispatch custom events (
document.createEvent()
is really awkward and verbose).Cheers.
@Miller,
The JS Signal stuff looks pretty interesting. I enjoyed, especially, that you included a Naming convention for the event types. Although naming stuff is supposed to be one of the hardest parts of computer science, I happen to think that naming event-types is especially difficult. I never know if I should to present tense or past tense... or if it should depend on whether or not the event can be cancelled.
@Guillaume,
It is definitely a bit strange, especially since every event type I've looked at up to this point has been string-based with a generic, flexible binding. But, while it is awkward, there is something that I really do like about it :)
@Miller,
Having the code-completion aspect is really nice. I use an Eclipse-based editor which, when dealing with Html / JavaScript, uses Aptana under the cover (or so I'm told); in general, this seems to have really good code completion - so, Im sure it would pick on these object-based events quite easily. Heck yeah :D