ActionScript signals, events and interfaces
Posted in: ActionScript
Consider the following interface by Diego Ponce de León (@diegoxleon):
/** @author xleon */ package quicker.core.api { import org.osflash.signals.Signal; public interface IVisible { function Show():void; function Hide():void; function Draw():void; function Clean():void; function get shown():Signal; function get hidden():Signal; function get drawn():Signal; function get cleaned():Signal; function get id():String; function set id(value:String):void; } }
Any class implementing IVisible must implement shown(), hidden(), drawn() and cleaned() and thus provide the four signals. Diego (and many others, including signals’ author Robert Penner) claim that this functionality is a great signals feature and that it cannot be achieved with Flex events. I have two reactions to this claim:
- Is it really that great a feature?
- The “can’t be done with events” claim is not actually correct
The rest of this post will attempt to justify both reactions. I’ll deal with them in reverse order.
The “can’t be done with events” claim is not actually correct
Whilst the IVisible example above is very neat, it doesn’t do anything that cannot be achieved with events. A signal nicely encapsulates three elements of an event: the dispatcher, the event type and its metadata (ie the data type associated with the event and thus the extra data that the listener can receive). With this in mind, we can rewrite IVisible to specify an event contract instead:
package quicker.core.api { import flash.events.Event; import flash.events.IEventDispatcher; public interface IVisible { function Show():void; function Hide():void; function Draw():void; function Clean():void; function get shownDispatcher():IEventDispatcher; function get shownEventType():String; function get shownMetaData():Event; function get hiddenDispatcher():IEventDispatcher; function get hiddenEventType():String; function get hiddenMetaData():Event; function get drawnDispatcher():IEventDispatcher; function get drawnEventType():String; function get drawnMetaData():Event; function get cleanedDispatcher():IEventDispatcher; function get cleanedEventType():String; function get cleanedMetaData():Event; function get id():String; function set id(value:String):void; } }
Clearly this is nowhere near as tidy as Diego’s signal-based interface, but that isn’t the point. The point is that it is possible to create an event-based contract through an interface. Further, with the aid of a helper class, we can even make it just as tidy as the signals example. The helper class is a value object that encapsulates an event’s signature, ie the dispatcher, event type and metadata associated with an event:
package xxx { import flash.events.Event; import flash.events.IEventDispatcher; public class EventSignature { protected var _dispatcher:IEventDispatcher; protected var _eventType:String; protected var _metadata:Event; public function EventSignature(dispatcher:IEventDispatcher, eventType:String, metadata:Event) { _dispatcher - dispatcher; _eventType = eventType; _metadata = metadata; } public function get dispatcher():IEventDispatcher { return _dispatcher; } public function get eventType():String { return _eventType; } public function get metadata():Event { return _metadata; } } }
Having defined this class, we can then rewrite our interface as:
package quicker.core.api { import xxx.EventSignature; public interface IVisible { function Show():void; function Hide():void; function Draw():void; function Clean():void; function get shown():EventSignature; function get hidden():EventSignature; function get drawn():EventSignature; function get cleaned():EventSignature; function get id():String; function set id(value:String):void; } }
Is it really that great a feature?
To my mind, neither the signal-based, nor the event-based solution, are really of much use as neither is a true interface contract. In both cases, there are two problems.
The first problem is that both specify vague compile-time contracts over the signal/ event listener. Until runtime, there is no way to know how many parameters, and of what type, the signal listener needs to be. Likewise until runtime, there is no way to know which event datatype the event listener needs to support.
The second problem is that events and signals are asynchronous and thus non-deterministic. Because of this, a class could implement any of the versions of IVisible above and yet never dispatch an event or signal.
To overcome these problems, ActionScript would need to implement built-in events with say C#-style delegates that specify the listener’s method signature. Further the compiler would have to check that any implementing class contains reachable code that will dispatch all events defined by the interface. I really can’t see this happening any time soon.
Conclusion
Signals is a great framework, of that there is no doubt in my mind. It has many really nice features, such as fire-once events, support for easily removing all listeners from a dispatcher and better type safety than events. One supposed advantage though is bogus in my view and that is claims related to using it with interfaces.
Return to: ActionScript signals, events and interfaces
Social Web