Thursday, September 9, 2010

Message Oriented Architecture (Code 1/2)

Here I present you the code of the EventCenter. I show your parts of code incrementally, to be able to add some comments at each step.
public class EventCenter {
   private static var instance:EventCenter = new EventCenter();
   private var hashChannel:Object;
   
   public function EventCenter():void {
      this.hashChannel = new Object();
   }
 
You have here the initial code of the class. We use the singleton pattern without any protection, you can improve that if you want but it's not the goal of this tutorial. The only one variable of this class is the hashChannel that is the HashMap that contains the different channels. In ActionScript 3, we don't have native HashMap like in Java, but we have the Object class that can contains dynamical property like an HashMap.
public static function reset():void {
      instance = new EventCenter();
   }

   public static function dispatchEvent(idChannel:String, type:String, args:Object = null):void {
      instance._dispatchEvent(idChannel, type, args);
   }

   public static function addEventListener(idChannel:String, listener:iEventListener, ...type):void {
      for each(var t:String in type) {
         instance._addEventListener(idChannel, t, listener);
      }
   }

   public static function removeEventListener(idChannel:String, listener:iEventListener, ...type):void {
      for each(var t:String in type) {
         instance._removeEventListener(idChannel, t, listener);
      }
   }
 
This part of the code is actually the interface we show to the world. All those methods are public static, we could also add final but it's out of the bounds of this tutorial. All the real code is hidden in the instance field. The reset method only serves if you want to reinitialize the EventCenter. The idChannel is the identifier of the channel on/from which we send/receive messages. The iEventListener listener is the object that will receives the message, we call its method onEvent with the correct arguments. Finally the ...type allows us to add as many type as we want thanks to the "..." syntax that will transform type into an Array.
public interface iEventListener {
   function onEvent(type:String, args:Object):void;
}
 
Here is the simple code for the interface. One iEventListener can listens to as many Channel as it wants. It is the time to show the concrete code:
private function _dispatchEvent(idChannel:String, type:String, args:Object):void {
   if(idChannel in hashChannel) {
      var hashType:Object = hashChannel[idChannel];
      if (type in hashType) {
         this._dispatch(hashType, type, args);
      } else {
         // No such listener
      }
   } else {
      // there is no listener for this sender
   }
}
 
We use the properties of objects to store elements in the pseudo HashMap. You can notice the "in" operator that is quite faster than the "hasProperty...". With this, we know if there is an entrance with key=idChannel, if so, we know there are some listener for that channel. Then, we search into the HashMap corresponding to the channel if there is the type we look for, in this case we can call the _dispatch sub-function.
private function _dispatch(hashType:Object, type:String, args:Object):void {
   var array:Array = hashType[arrayType] as Array;
   if (!array) { // first assert
      // The array is null
      delete hashType[type];
   } else {
      if (array.length == 0) { // second assert
         // Array is empty => property removed
         delete hashType[arrayType];
      } else {
         for each(var listener:iEventListener in array) {
            listener.onEvent(type, args);
         }
      }
   }
}
 
As you will see in other function, especially in "_removeEventListener" we never keep an Array of listeners empty or null, so I call the two conditions "assert". Normally we never came into their braces.
This function allow us to dispatch / trigger / fire an event to all the corresponding listeners. The args contains also an HashMap.
Here is some examples of use:
EventCenter.dispatchEvent("Score_Channel", "Reset_Score");
EventCenter.dispatchEvent("Score_Channel", "Add_Score", {incr:13});
EventCenter.dispatchEvent("Score_Channel", "Decrease_Score", {decr:11});
EventCenter.dispatchEvent("Score_Channel", "Change_Score", {old:12, new:39, forPlayer:'Bob'});
 
The args is optionnal and moreover could have as many item as you want, identified by the keys. I think this is the most flexible we can do. Remember how complex it is with the native system of event. You have to create a new type of Event for each... new type you want. But with my solution, you just have to change the name. For my use, I always make constants representing the events with in commentary the format of the keys, with that when I code a dispatch, I always see with the doc, what is the format. Next part of the code, in next message.

No comments:

Post a Comment