Tuesday, October 09, 2007

Beginner OOP: Observer and Event Delegation Patterns (Part 2)

Now remember last time we talked about the observer design pattern. The observer pattern is very useful because it centralizes information and allows coordination of the interactions between objects.

But there's more. Let's see what's behind door number 2.

The event delegation design pattern is a similar programming strategy. The difference is that this pattern allows multiple types of events. For example, if we write a program which is able to draw a piano keyboard, we might create a class to represent a single key on the keyboard. This single key object might listen to a data model. Now when our keyboard data model tells us that C# is pressed we're not interested in updating the visuals for every key on our keyboard we're only interested in one key. With the event delegation pattern we're able to dispatch a single specific event that will allow only the objects subscribed to this event to be triggered.

Let's talk about terminology. Delegates are objects interested in events. Delegates listen to a specific event fired from a dispatcher. Let's take a look at some simple code to create an event dispatcher. Our event types are actually represented using strings.

To draw an analogy between the observer pattern and the event delegation pattern: Dispatchers are like observables, delegates are like observers, and events are like updates.

class EventHandler {
private var _hash:Object;

public function EventHandler() { }

private function _getListenerIndex($queue:Array, $obj:Object, $listener:String):Number {
for(var i:Number=$queue.length-1; i>=0 &&$queue; i--) {
if($queue[i].l == $listener && $queue[i].o == $obj) return i;
}
return -1; // didn't find anything
}

public function addEventListener($type:String, $obj:Object, $listener:String, $useCapture:Boolean, $priority:Number, $useWeakReference:Boolean):Void {
if(_hash===undefined) _hash={};
if(_hash[$type]===undefined) _hash[$type]=[];
var i:Number=_getListenerIndex(_hash[$type], $obj, $listener);
if(i==-1) {
_hash[$type].push({o:$obj, l:$listener});
}
}

public function dispatchEvent($event:Object):Boolean {
if(!hasEventListener($event.type)) return false;
var queue:Array=_hash[$event.type];
for(var i:Number=queue.length-1; i>=0 &&queue; i--) {
var obj:Object = queue[i].o;
var fxn:String = queue[i].l;
obj[fxn]($event);
}
return true;
}

public function hasEventListener($type:String):Boolean {
if(_hash[$type]!=null && _hash[$type].length>0) return true;
return false;
}

public function removeEventListener($type:String, $obj:Object, $listener:String, $useCapture:Boolean):Void {
if($type!=undefined && $listener==undefined) {
delete(_hash[$type]); return;
}
var i:Number = _getListenerIndex(_hash[$type], $obj, $listener) if(i!=-1)
_hash[$type].splice(i,1);
}

public function removeAllEventListeners():Void {
delete(_hash); }
}

In the above code, we have an object, our hash, which acts as an index for our different event types. Each event type then has a list of delegates. These delegates have methods which will run in their respective scopes.

The addEventListener function will first locates the event list, then if the delegate is not already on the list, it pushes the delegate to the list. When the event occurs our dispatchEvent function locate the event list and call each delegate on the list notifying them that the event has occurred. We also include a removeEventListener function to remove delegates from events.

And there we have it.

After Thought
Now, you might ask why would anyone want to write a whole new event delegation class. Aren't there classes written in Flash which accomplish this? Partly, Flash MX and other provided classes do not run code in their intended functional scopes. This creates a large problem since event delegation is typically deployed inter-object. Classes written to satisfy this need are mostly external classes outside of Flash. Grant Skinner has written a great event class called GDispatcher.

ActionScript 3.0 now uses a similar event model which allows functions to run in their proper scope. In addition, most classes in AS3 now inherit from the EventDispatcher class.

No comments: