Post navigation

Smartypants-ioc and As3-signals example

During my recent presentation at LFPUG, Tink was very persistent in suggesting that the event bus used in most MVC frameworks might not be the best solution. This got me thinking about what it would be like to do MVC without an event bus. And so began this little experiment.

You can take away my event bus, but don’t take away my dependency injection.

My project was to create the same podcast application as I used in my various framework examples, but this time without any form of central event bus. Initial thoughts suggested I would need a dependency injection container to maintain loose coupling, so I grabbed my favourite one – SmartyPants-ioc, created by Josh MacDonald.

Using SmartyPants and lots of interfaces I can inject my objects where they’re needed while only creating dependencies on the interfaces not the actual implementations. Thus (hopefully) I can avoid needing an event bus for communication. For example, view mediators (I used the presentation model pattern) have controllers injected into them and call their methods directly. But they only know the interface of the controller, not the actual implementation.

This looks good to start with, but unfortunately I still need events for some things. The obvious example is notification of results from asynchronous service calls, but notification of changes to the model also works well when handled by events. Because I don’t have an event bus, these events will be dispatched by specific objects and must be listened for on those specific objects too.

Does that object dispatch an event or not?

Unfortunately I’m coding to interfaces, and there’s no way to define what events must be dispatched from a class that implements an interface. I could work on trust – assume any object that implements the FeedLoader interface will dispatch a feedLoadComplete event, for example. But if I want my solution to be truly practical, I need something more robust than that.

One of the many nice features of As3-signals, created by Robert Penner, is that the signals are properties of classes so they can be specified in an interface. For this reason I chose to use As3-signals for my events.

For example, the previously mentioned FeedLoader interface defines a getter for the feedLoadCompleteSignal. Now any class that implements this interface must have that getter, and any class injected with an implementation of this interface can trust that that getter will be there, and so listen for the underlying signal.

In conclusion

All in all I think it worked out rather well. There may even be a potential future in an architecture like this. I do fear the explosion of interfaces and injection rules may make the approach unwieldy for a large project, but perhaps multiple DI configuration classes, a clear package structure and disciplined developers is all that’s required. I welcome your opinions on this.

The example is below. Right click on the swf to view the source. Links to the framework versions follow. All versions have identical functionality.

25 thoughts on “Smartypants-ioc and As3-signals example”

Excellent! I like how your Signals ignore capital-E Events completely. One Signal dispatches a Feed, others dispatch Podcast, Array, or String. Your feedsLoadFailedSignal calls listeners without sending any arguments, because it doesn’t need to. By contrast, with EventDispatcher, we must create a new Event instance in addition to the value objects. Because event.target is immutable, the events can’t be object pooled.

Signals’ possibilities for reducing object creation could potentially help performance in a large application. While it’s true that one would have many Signal instances, these are generally created at startup. Getters for Signals can use lazy instantiation to save memory. Event or value objects sent from Signals can be object pooled.

I must admit it is an interesting approach. It is a bit hard to figure out where the actual code is due to all those interfaces. Also the injection rules don’t tell you much what where those injections take place. They are all the same, while in any framework you have commandMaps, actions, etc. if you know what I mean.
But this definitely proves you don’t need events to do it. The question is why wouldn’t you want to use events? I understand Robert’s critique on events, but I personally think events are nat as bad as he describes. You just need to know how to use them (screw weak references
I don’t think there is a need of replacing events, because everyone knows them. Creating architecture based on signals and DI will have a really steep learning curve because this essentialy will completely change the way you will have to write code and I don’t think people will like it.

Once you use events you can’t guarantee that something is listening for them and will act on them.

This is one of the main advantages of events. This is great when used at a component level (as you don’t want to listen to all events on a ComboBox). You want to pick the ones your interested in, and because they are events you can pick and choose which events you want to act on.

When it comes to a framework level though I would question why you would dispatch an event if you don’t want a guarantee something is picking it up, why do you require that very loose coupling.

For instance lets say you have a simple app with one Controller picking up 10 events from your view and running commands. You swap out this Controller for another, but don’t notice that its only picking up 5 of the events. There’s no way for your editor to give you errors, and at runtime you won’t get errors, instead just a silent fail. You now have to work out why when you click on the items in the View, the action doesn’t take place. Or lets say you can see a Command getting invoked when it shouldn’t. You now need to trawl through your Views to try and match up the events.

Now lets say instead of the event, you injected the Controller into your view, and your View called a method on it directly. Your Controller then invoked the correct Command. You again switch the Controller. If it was typed to a interface you would get immediate errors on any methods that were missing, or if you did a complete swap (i.e. same name same package), and anything was missing you’d also get errors for any missing methods. Lets say all the methods are there but there’s a problem and one is being invoked too often. You could jump in change that method name, and you’ll get errors throughout your project where this method is called.

Tom, it’s not that I didn’t want to use events. It’s rather that I didn’t want to use a central event bus.

I also wanted to avoid tight coupling, which is why I chose to inject objects based on interfaces. And that’s where events failed me, because they can’t be specified in an interface. And that’s why I chose to use Signals instead.

So ultimately the reason for not using events is because they can’t be specified in an interface.

I’m thinking some more about the APIs… Since most people won’t need the extra features of priority, bubbling and IEvent, I’d like to put more focus on SimpleSignal. I am considering renaming SimpleSignal to Signal, and the current Signal to something like DeluxeSignal.

Your use of the signal interfaces in your getters was very clever. I love how you typed the private var to Signal, but the public getter to IListeners. I’m so accustomed to typing the private and public parts of a getter to exactly the same class or interface. I would have resorted to casting to IDispatcher when I want to dispatch. But I like your approach much better.

I’m also thinking about restructuring the interfaces. The composite interfaces that merely combine two other interfaces (ISignal, etc.) seem pointless now. I should just have Signal implement the two interfaces directly.

Also, I don’t know if “IListeners” reads well in an interface, and I don’t like it being a plural noun. From your example, I realise that the APIs in IListeners are the signal from a collaborator’s point of view.

I like your idea of making SimpleSignal the default. It would encourage developers to only use the more complex signal types if they really need it. It also indicates a direction for other, as yet unimagined, signal types in that one can’t get (much) simpler than the simple Signal, but one may add features to create other, more complex, signal types when required.

I also like the idea of renaming IListeners to ISignal because the IListeners interface is what makes a signal a signal. Who cares how we dispatch signals (only the dispatcher), it’s how we listen to them that makes them what they are – a signal without a public dispatch method is perfectly possible (a signal may be automatically dispatched based on a timer, for example), but a signal without add and remove methods is hard to imagine.

I made yet more changes that move further away from events. Inspired by your example, I removed the “event” terminology and now Signal dispatches “value objects”, validated by “value classes”.

I enabled the Signal to define any number of value classes in the constructor:

public function Signal(…valueClasses)

dispatch() still sends variable args but it’s cleaner:

public function dispatch(…valueObjects):void

Inside dispatch(), the valueClasses Array is used to check each item in valueObjects.

Conceptually, Signal is now a multicast delegate that can define a function signature. AS3 doesn’t give us compile-time type safety for the signature but Signal does validation at run-time: dispatch() will blow up quickly if the wrong value objects are passed. But the strictness is opt-in.

Another minor change that will require search and replace in your code: Signal no longer has a target property or constructor argument. Signal doesn’t have a use for target like DeluxeSignal and NativeSignal do.

Hey. I really like your reasoning on events overuse.
I was walking in circles around the idea of using signals with DI. Though my lack of experience stopped me from implementing it.
Thanks for making it clear for the rest of us.