the easiest way to do that would be to have a strongly typed object which contains the signals (let's call it a signal bus), a single instance of which is injected or passed into all of the modules.
I would great multiple interfaces for the signal bus with getters for the different signals.
Then Module A only needs to be coupled to an interface containing the signals that Modules B, C and D might dispatch that it's interested in, and not to the module directly, or even the whole signal bus (otherwise you could be looking at having to recompile multiple modules when you add a new signal).
In the signal bus itself I'd probably add a locking mechanism so that the signal is actually relayed, something like:
public function dispatchUsernameUpdatedSignal(username:String, senderModule:ModuleTypeA):void
this would prevent module A from being able to send module B signals.
Again you'd want to individually interface this to prevent the actual signal bus from being compiled into every module. The concrete signal bus class would only be required in the shell that instantiates it and passes or injects it to the other modules.
This could be overkill - there's a whole bunch of less rigorous versions of this you could implement - just drop what you don't need.
I've thought about this a lot, and in my case recompiling is something I'd like to avoid, so my modular app is happy with events. They do have their benefits in these situations.
Hope that's helpful - far too early on a Saturday here so please excuse any rubbish thinking or typos!
Support Staff4 Posted by Joel Hooks on 29 May, 2010 03:11 PM
I'm really not a fan of the SignalBus concept. Now you get a
(likely) bloated manager class that is responsible for and provides
unfettered access to n+x Signals.
I'd favor a CommonSignalLib that was shared by the modules. It
would contain strongly typed Signal extensions that would be mapped
for injection in the shell. The child Contexts would have them
available for injection for use in your modules.
Agreed, I think it's straying into controller territory, though of course if you started to feel there were too many in one class you could break it up - as long as you honoured the interfaces that would be a low-impact refactor.
Say more about the strongly typed Signal / CommonSignalLib thing... do you mean injecting the signals into the dispatcher and the recipient? That definitely works but I'm still not sold on there being enough extra 'win' over Events, as you have to create a new class for each signal anyway.
It would depend I guess on why you wanted Signals rather than Events. Synchronicity would be a strong case.
Support Staff6 Posted by Joel Hooks on 30 May, 2010 02:36 PM
I don't have any issues with making lots of
single-responsibility Signal classes. You can type their values
explicitly and they are just generally strong OO where Events are
not. The Signals can then be added to interfaces and injected as
needed. Injecting them into the dispatcher and recipient clearly
defines the relationships the classes have with one another and
avoids the grossness of a controller. The only cost here, imo, is
the extra classes. I really don't mind having finely grained class
level responsibilities in my applications though.
A modular Robolegs-Signals example is in the works!
I'm into extra classes too - I have a lot of strongly typed events, with strongly typed parameters - none of which are optional.
I'm clearly missing something here - help me understand what it is...
I'm using (and loving signals) where ClassA does
I can see that there's an additional level of checking, because - unlike events - you can't listen for a Signal which the class will never dispatch. Lovely. The interface of classB tells me that this signal is going to be available. Great! Particularly fab for view->mediator relationships.
But where you're injecting SomeSignal into 2 modules, so that moduleA (rightly) has no knowledge of moduleB ... surely moduleA can still be listening on a signal that is of no relevance to moduleB (or any other module). Only the developer knows whether they have injected the signal somewhere where it might be dispatched.
If you're not actually injecting the signal at both ends, and instead one module is relying on an interface on the other, then I think that's a possible disadvantage, because you have to actually make an instance of ModuleB available to ModuleA, and you're now compiling IModuleB (or a part of it) into ModuleA.
And if you're injecting at both ends (into the dispatcher and the recipient) then there's no more guarantee than with an Event that you're listening for something 'real'.
I get that in many situations Signals bring an advantage over Events, but I don't see how what you're describing is better than strongly typed Events - other than the synchronous nature, or the possibly (fractionally) shorter code.
There's no compile time type checking of the Signal dispatch/handler relationship as far as I know, so in some ways you get better compile time checking with a multi-property strongly typed Event, where properties are strongly typed, because you only have to get one thing right: the event type. The dispatch/handler relationship can involve multiple properties, which have to be the correct type as well as in order - hence I'm favouring Signals for no-property or single-value dispatches, but events for where I need to pass more stuff. Otherwise I guess I could pass a TypedVO but that feels like no gain over events at all!
Am I confused here? I must be missing something key! How are they stronger OO than (Strongly typed) Events if you're Injecting at both ends? Is it because the Signal is a property of each module, so it illustrates their intent to use it? That's what the redispatch stuff in the ModuleMediator does for me - so I'm a fan, but not sure Signals are a better way to achieve it.
Support Staff9 Posted by Joel Hooks on 31 May, 2010 04:57 PM
I don't think ModuleA can ever really know (or care) that what
it is listening for is "real" in the sense that it may or may not
get dispatched from anywhere. In this regard events/signals are in
parity. There is no way to force a contract across modules to be
sure that moduleA is going to strictly receive a Signal from
Signals are "more" object-oriented in the sense that they are
actual properties of the classes that listen to and dispatch from
them. The very fact that they can be injected is to me innately
more OO than events. I don't know that their use is more
appropriate in the case of inter-module communication, but the post
here is about how to do it. I just don't like the controller/bus
approach to signals. It masks too much information. One of my
favorite aspects of Signals is that by injecting them individually
I am instantly clued in when my class is doing too much. Injecting
a ton of Signals? Your class is probably breaking SRP. It is
painfully obvious very quickly.
Frankly, there are a lot of circumstances where I would favor
Events over Signals simply because Events are native and understood
by Flash developers. There is something to be said for basic
conventions. So I'm not really trying to persuade you to use
Signals over Events ;)
I'm very much with you on the SRP flag with Signals.
I like to play the Sesame Street Favourite - "One of these things is not like the others... " and if I one of my Signals doesn't fit then it's time for a refactor.
And I agree on the grossness of the bus - I don't like container classes for anything other than user-info static string constants (app error messages and the like).
Bear with me, but here's my mental analogy of the differences:
A signal bus is like an octopus, reaching into many modules with its tentacles, and permanently joined at the middle.
Injected signals are like pieces of string - hopefully there's something else at the other end, but who knows. Depending on your implementation it can be beautifully woven or a total tangle. A big advantage is that if you tug the string, that's felt instantly at the other end.
Events are like bees, visiting their listeners in no particular order, and largely in their own sweet time.
As you say - pros and cons.
I'm glad this is helpful Tom.
Interesting stuff - especially as the module is kind of a meta version of general class-to-class implementations. One thing Joel and I very much agree on - more specific classes is a good thing (assuming they're named and packaged usefully). Whether those should be events or signals probably comes down to the specifics of each individual use-case. If you were truly looking for best practice you'd probably mix the two!
I get that in many situations Signals bring an advantage over
Events, but I don't see how what you're describing is better than
strongly typed Events - other than the synchronous nature, or the
possibly (fractionally) shorter code.
This is kind of a tangent, but I don't think signals are any
more synchronous than events. E.g., if you add something like
before dispatching foo
after dispatching foo
. Things get trickier on the display list, but I imagine the
same thing would happen with signals if they could be used in place
of events there. Or is there a particular case you're thinking of
that I'm missing?
Interesting reading... I was just about to post a question on
Signals usage in a modular application. I like Signals but agree
with Joel's comment in relation to favouring Events over Signals in
certain circumstances. I fear Signals may cause problems within
large teams contributing to enterprise flash/flex applications.
Maybe it's to early to adopt Signals in these instances?
@RayReadyRay: The main application class in your modular example seems to have the wrong name. I'ts referred to as "ModularSignalDoodads" in context and mediator, but actually has Joel's original name "ModularDoodads". It has to be renamed for the example to run. Thanks for sharing anyway!
As far as I know we can get signals to modules from other
modules via adding callbacks to child injectors injected
signals(contained in other objects,eg models). I guess to emit a
signal from a module to call a command elsewhere in another context
we just need to reflect upwards from a shared signalbus :)