Problem

The Flex Framework is a powerful and rich environment. However due to its complex and elaborate event driven architecture it is easy to loose control and it is hard to maintain a clear communication among all components / objects.

Passing instances down a deep container path allows for poor coupling and using local events requires each listening object to be aware of the instance / existence of the dispatching object in order to register itself as a listener. Using the Capture and Bubble phases does not solve the problem as it is inherently (and humanly) hard to follow and only applies to display objects.


Solution:

Adopt a simple yet powerful and reusable system such as the Autonomous Linking Object Network Design Pattern (ALON design pattern).

Don't be scared by its long definition, the ALON design pattern is simple to understand, even simpler to implement and will save you countless hours of having to reinvent the wheel.

In short, the ALON design pattern creates a single channel which all communication is piped through. The self-governing, independent class is named ComBroker (Communication Broker) and it is the Application's main communication channel. It is the Broker's responsibility to act as the medium for all components / object communication.

Communication is broken down into two types, Direct and Event driven.
Each communication type is independent of each other and may be used to solve a different challenge.



1. Direct: In Direct communication, each object which has public methods, registers itself with the Broker with a unique name; thus allowing other objects to acquire a reference to the registered object by asking the Broker for its unique known name. Once a requesting Object has received the reference from the broker, it can begin to participate in direct communication with the returned object instance. You may also opt to use an interface for a registered object to reduce its public service exposure (although you don't have to).




2. Event: In Event communication, all event listeners register themselves in the scope of the Broker. Also, all event dispatchers are dispatched within the scope of the Broker.

This allows for a single global channel for both listening and dispatching of events. Two or more components can communicate effortlessly regardless of how deep each component is buried inside the component level structure. Components do not have to know of the existence of one another in order to register themselves as listeners for a particular event string.

All event strings are maintained in a global self documenting constant static file.






Discussion (Part1):



Design Overview for Event Communication:

Event communication is the language of choice between components and objects in Flex. It allows for good decoupling, best practice object oriented programming design and best of all nd easy way to code and debug. 

With events, you can pass information from one object (a dispatching object) to one or more (as many as you want listening) objects.

The message that is passed is in a form a custom event class which holds both a reference to the dispatching object as well as any other properties ( of any type such as String, Boolean, Object, Number etc … ) which you want to include in the dispatching object's message.

Use a global centralized (antonymous) system which controls both the registration of a objects listeners and dispatcher eliminating the need to pass references to instances down from one component to another component.

Thus, you will no longer need global variables which hold references to objects.
The event controller class is called the ComBroker and all communication is accomplished within its scope.



Between direct and event communication you are provided a complete solution solid communication of one to one or one to many using the ALON design pattern ( note that direct communication will be covered below in Part2 of the document ).

Ok, so you are sold. If you read so far you are probably interested in incorporating the event communication and direct communication using the ALON design pattern, so read on.




Event Communication Implementation:

Implementing the Event Communication is simple. You only have to code it once and you can reuse it over and over in all applications.

First create a custom event.

The custom event will be used every time one of your components / objects wishes to dispatch an event (i.e.: notify the world that something has occurred). Our custom event has a general name since it is a very generic class, ObjectDataEvent.

The code is as follows:

package mylib.events

{

import flash.events.Event;

public class ObjectDataEvent extends Event{

public var instance:Object;
public var values:Array = new Array();

public function ObjectDataEvent(type:String,data:Object, ... args){

super(type);
this.instance = data;
this.values = args;

}

override public function clone():Event {
return new ObjectDataEvent(type, instance);
}
}
}

You can think of this class / object as the envelope that is used to carry a message from the dispatcher to listeners every time a dispatch event is fired. It is the responsibility of the dispatcher to instantiate the ObjectDataEvent class and pass it the proper arguments if any exist.

If we examine the code we can basically see that this is a standard class which extends the event class.

Now lets examine the arguments to the class's constructer. First, is the type. The type is just a string, any string that will be used to identify the event and the objects that listens to the event string. A good example for such a string can be "EXIT_APPLICATION". So whenever an object listens to the event of EXIT_APPLICATION within the scope of the Broker, and anytime an object dispatches an event with the string of EXIT_APPLICATION within the scope of the event broker, the envelope ( i.e.: our custom ObjectDataEvent ) is instantiated and passed to the listening object ( this is done automatically by the Flex Framework ).

The second argument is of type object, and it will hold a reference to the dispatching object. This allows a way to access the dispatcher from the listener, we will see that shortly.

Third, are the args argument, which allow you to include any number of optional arguments which you may wish to pass and store in the envelope / class. Since an array is used to hold the args you can store any type of native simple or complex data structures and conveniently access them later from the listener using the array notation.

Next we need to create the ComBroker class. The ComBroker is very simple. In fact it is so simple it holds nothing.








package mylib.events
{

public class ComBroker extends EventDispatcher
{
}
}

The reason we need the ComBroker class is because we will use its scope as a shared space where events from all over the applications can register listeners and dispatched events can fire.

Later on during the Direct Communication implementation we will add additional code which is needed for Direct Com. However, for the sake of Event Communication, nothing else is needed within the ComBroker.


Next, we need to define a Global constant file which will be used to hold all of the strings which the Flex framework will use to pair listening and dispatching components. Using such a global file is Vidal to the success of the ALON design pattern as it insures you do not re-use strings which are already registered for some type of event process. Also, such a file provides self documentation as you may always refer back to it for an overview of what is being fired inside the app.

It should be noted that if your project is very large, you can split the Global constant definition file to smaller files each with its own purpose. For example, you can have one definition file only for application events while another definition file can take care of XML RPC events.

Here is an example of a definition file:

package mylib.events
{
public class EventsGlobals {

public static const EXIT_APPLICATION:String =
"EXIT_APPLICATION ";

}
}


Having a Global config file is a good design practice as you will normally want certain read only properties / constants to be available everywhere to all components.

The Global file should instantiate a single copy of the ComBroker class ( you can read more about the singleton design pattern to enforce a single broker if you wish for a cleaner approach ). By defining the Globs class broker as public static we are instantiating the existence of the broker as soon as the application starts. This in effect allows global access to our Broker which is used throughout the application as soon as our code runs.

Accessing the broker instance from anywhere in the application is as easy as typing:

Globs.broker……..






package mylib.events {

 public class Globs {

public static var broker:ComBroker = new ComBroker();

}
}




Now lets assume you have an application which follows the structure as in diagram (1):

 ALON

Your Application ( the outer red ball ) has a component named orangeBall under Application -> orangeBall. The broker instance ( blue circle ) also lives inside the main Application. We also have a component named marbleBall which lives inside application -> hbox -> marbleBall and finally we have a whiteBall which lives inside Application -> hbox -> vbox -> whiteBall.

Now in order for any component to communicate with any other component regardless where it exists and pass any information ( typed as Boolean, string or anything else ) simply register listeners and fire dispatchers in the Globs.broker name space.

Lets assume that our whiteBall wants to listen to the orangeBall anytime the orangeBall fires an Exit Application message. And lets assumes the marbleBall wants to listen to the same message from the orange ball. Within the whiteBall scope do:

Globs.broker.addEventListener(EventsGlobals.EXIT_APPLICATION,this.showSomething");

Within the marbleBall scope do:

Globs.broker.addEventListener(EventsGlobals.EXIT_APPLICATION,this.showSomethingElse");
Of course you will have to have showSomething and showSomethingElse as public methods which exist in whiteBall and in marbleBall respectively.

The function should look like:

public method showSomething(event:ObjectDataEoid {

}

So it accepts our envelope which will contain everything we need.

Anytime you wish to dispatch an event from within orangeBall simply execute:

Globs.broker.dispatchEvent(new ObjectDataEvent(EventsGlobals. (EventsGlobals.EXIT_APPLICATION,this));

The listening components such as our whiteBall will receive a notification and may access the dispatching component inside the showSomething method which exists in the whiteBall scope.

For example, the following line of code will show ( trace ) the instance name of the dispatching orangeBall object from within the scope of the whiteBall listening component.

public method showSomething(event:EventsGlobals):void {
  trace(event.instance)
}

If you review the constructor of the EventsGlobals you will see that it accepts any number of additional arguments. So you may also dispatch and include additional information from within the dispatching orangeBall. For example, you may do:

Globs.broker.dispatchEvent(new ObjectDataEvent(EventsGlobals. (EventsGlobals.EXIT_APPLICATION,this,"valueA","valueB"));

You can now also access both valueA and valueB from within the whiteBall simply by doing:

public method showSomething(event:ObjectDataEvent):void {
  trace(event.instance); // instance name of dispatcher
  trace(event.values[0]); // shows valueA
  trace(event.values[1]); // shows valueB

}







Memory Management:

Flash uses the garbage collector to manage its own memory. Although memory is a commodity that is easy to come by these days, a good design pattern and best practices drive good memory management. The garbage collector uses a reference count to instances of objects to determine when an object should be removed and memory relocated to the system. In other words, weak references are no longer needed.
However, addEventListener occupies the memory location until the removeEventListener is called.

Using the ALON design pattern allows for a simple and easy to manage memory allocation and release system. If you have a class which generates many addEventListener which are only used during a specific execution of your application and than discards, you may create a second Broker ( we will call it TempBroker ) and assign all the so called temporary addEventListeners to it. When you are ready to discard all of the temporary event listeners you can simply removeEventListener for all objects which have registered with the TempBroker Class and continue by deleting it ( loosing a reference to it).
It should be noted that the advantage of the antonymous unified event broker eliminates a need for a weak referenced listeners. Weak referenced listeners are turned on by setting the 5th optional argument of the addEventListener to true such as:

target.addEventListener(MouseEvent.CLICK, onClick, false, 0, true)

When not using the ALON design pattern, and as your application grows, it is very likely that you will delete an object in one location of the application without actually removing the event listener referencing it from another location.

This may result due to proper decoupling of objects in the Flex framework having one object unaware of the existence of another object (decoupling is a best practice and considered a good design pattern by the way ) or simply because you forgot to do so due to the sheer size and complexity of your code.

However, since the ALON design pattern centralizes all listeners into a single class, simply by removing all listeners to the Temporary Broker Class and than deleting (losing reference) to the TempBroker class itself, you are insuring the garbage collector will do its job and release the memory back to the system.






Taking advantage of the Capture and Bubble phases with the ALON design pattern:

Because the Broker class extends the EventDispatcher and does not inherit from the display object, it does not participate in the capture and bubble effects which are powerful and useful.

However, is it easy to extend the functionally of the capture and bubble events into the ALON design pattern simply by using hooks.

For example, let's say you are interested in capturing an event of a button being clicked by the user on the way down from the Application container down through the container where the button instance lives. You also wish cancel the event when you capture it during the capture phase (so it does not propagate downwards any further) and than continue by notifying our Broker that the event has been captured and stopped so the Broker may take the appropriate action and notify all global listeners.

You can start by setting a hook to listen for the button click event at the scope of your Application container (remember to set the 3rd argument of the event listener to true for event dispatching to work during the capture phase).

addEventListener(MouseClick.CLICK, onStageClick, true)

Next define the function to be fired when the button is clicked and stop it from propagating downwards, and than continue by defining the Hook to notify our Broker.



Public function onStageClick(event:MouseEvent):void {
event.stopImmediatePropagation();

Globs.broker.dispatchEvent(new ObjectDataEvent(EventsApp.ADMINISTRATE_MODE,this));
}

This will stop the downwards propagation eliminating duplicate dispatching of an event and notifying our Broker to go ahead and notify all listeners whom registered for the string of ADMINSTRATE_MODE.

You have now extended the power of the Capture and Bubble event phases into your application while maintaining the uniformity with the ALON design pattern simplifying management and debugging of the event model.






Optionally, implement and interface to increase decoupling:

Use you may use an interface to reduce the exposure of an object to allow other object to use only the allowed methods. See part2 for more info on intraces and how to implemt them in Flex 2.0



Discussion (Part2):




Design Overview for Direct Communication:

With Direct communication each object which has services (public methods) to offer to others, registers its reference with the ComBroker using a unique string (name) and other objects may request a reference to the registered object to communicate with it. It's the ComBroker's responsibility to offer services through its public methods of getService and setService allowing objects to get services and set services through it regardless of where each object lives ( how deep within the container level ).

The Direct ComBroker completes the ALON design pattern as It allows any object to register itself as a service provider with the ComBroker. Any other object may request services from the ComBroker by asking the ComBroker for the a reference to the registering object instance name. Once the request is answered, the requesting object may begin to participate in direct communication with the object. This allows for direct communication from any object to any object.

You may think of this as a unicast communication solution. The previous event driven communication pattern resembles a multicast solution where any registered object can listen to any dispatched event on the underline channel.

The ComBroker builds and extends on our previous code. Previously we used the ComBroker simply as shared memory. However with the ComBroker direct services additional code must be added:


package mylib.events
{

import flash.events.EventDispatcher;
import dynaLib.common.interfaces.*;

public class ComBroker extends EventDispatcher
{

private var _registry:Object;

public function ComBroker() {
_registry = new Object();
}

public function setService(name:String , obj:IService):void {
trace("regsitered " + name + " " + obj);
_registry[name] = obj;
}

public function getService(name:String):IService {

if ( _registry[name] == null ) {
return null;
} else {
return _registry[name];
}

}

}
}


The previous code illustrates three things. First the constructor creates a new object which is used as a hash key table.

Next is the setService. The set service is called from any object who wishes to make its services available to the world through the ComBroker.

An object will register itself with the following command:

Globs.broker.setService("NavTree",this);

The Navigation Tree component registered its instance name ( this ) with its unique string called "NavTree". It would be highly recomened that you use a staic constant file / variable such as the one used in the event communication

for example:
Globs.broker.setService(EventsGlobals.NAV_TREE,this);

Using global constant static variables insure that you do not use the same string twice and provides self documenting code.

One important element of the ComBroker direct service is its reliance on interfaces. If you look at the source code of the ComBroker you will notice that the second argument ( the one which we passed 'this' to ) is types as IService. This should suggest to you that the registering object ( the one wishing to provide services ) must implement the IService interface or another interface which inherits from IService.

The IService implementation is as follow:


package mylib.events.interfaces
{
public interface IService
{

// Implements nothing

}
}

To implement the IService in a component / Object simply insert the following in the header of the component:

<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="100" height="200" implements="dynaLib.common.interfaces.IService">

<mx:Script>
<![CDATA[ …….


As you can see from the above example the IService implementation is empty ( no methods ).
The idea behind it is that additional components which use the IService interface will extend the interface. The following is an example of the NavTree component interface which implements and extends the IService.

package mylib.events.interfaces
{

import flash.events.Event;

public interface ITree extends IService{

function updateTreeIcons(event:Event):void;

}
}

To recap, we have typed ( set the type of ) the second argument of the setService method to IService. This enforces objects who wish to register themselves as service providers with the ComBroker, to implement the IService or an extended IService interface in order to provide services.

All that's left now is for a requesting component / object to request services using the getService method such as:

Globs.broker.getService("NavTree");

Or the equivalent using a global constant file:

Globs.broker.getService(EventsGlobals.NAV_TREE);

The ComBroker in reply will return a reference to the registered object which had registered under the string NAV_TREE / "NavTree" and both objects can begin talking at will.

One last thing to note is that during compile time the compiler has no idea as to which service ( i.e.: which type ) the ComBroker will return. Therefore you must cast the returned reference of getService to the proper type such as the following example:

var myTree:ITree = Globs.broker.getService("NavTree") as ITree;

This allows any object to access any public method of "NavTree" using the implementation of ITree and to expose only the available public interface.

It is a good idea to check if an object has registered with the ComBroker for direct communication before you begin talking to it to prevent an exception. This can be achieved simply by doing:

var myTree:ITree = Globs.broker.getService("NavTree") as ITree;
if ( myTree == null ) {
trace("Not available");
} else {
trace("Available");
}




Conclusion (Part3):



The ALON ComBroker is the "magic bullet" solution for all communications within the Flex framework. Unicast ( direct ) and Multicast ( Event ) communication provides everything you need within the ALON design pattern guidelines. You may also easily extend the functionality of the ALON design pattern. For example, you may create a broadcast functionally which allows all objects ( direct or event ) to be notified of an event which occurs in the application.

You may also modify the envelope / custom even object to accommodate further custom needs which may come up during your application development.

Even if you have not touched your code in months, when you start working with it again, as soon as you remember that you adopted the ALON design pattern you will remember that all events are controlled from a single point tracing back event registration ( Listeners ) and executing ( Dispatching ) will be straight forward and easy to follow.

This is also true for the Direct Communication as it allows you to easily follow the code to the source and read the interface implementation so you may be exposed to the capabilities of the servicing object.

I hope this document helped.

Please email me comments ( good or bad ) to helihobby@yahoo.com as I would appreciate all the feedback.

You can also reply to this article at the Yahoo Flex Groups so everyone can read you comments:

http://tech.groups.yahoo.com/group/flexcoders/message/66101


Click here to go back to the Home Page.


Sean.