Cairngorm Patterns: Batch Commands
Posted by Steve Brownlee on March 31, 2009Mar 31
Related Posts
Basic Cairngorm Commands
In the base implementation of the Cairngorm micro-architecture, events and commands are mapped on a 1-1 basis. For every invoker (event) you have to specify one command in your controller.
public function Controller()
{
super();
addCommand(TeammateEvent.LOAD_TEAMMATES, LoadTeammatesCommand);
addCommand(EffortEvent.LOAD_TYPES, LoadEffortTypesCommand);
addCommand(ProjectEvent.LOAD_PROJECTS, LoadProjectsCommand);
addCommand(TeammateEvent.LOAD_TEAMMATE_ROLES, LoadTeammateRolesCommand);
...
}
For most cases, this is perfectly fine, but in a data-heavy model, such as the one being built for my current application, I need to initialize the application and fire off many individual events in order to load my model with base data.
// Load all teammates in the database for display in the reports tab
new TeammateEvent(TeammateEvent.LOAD_TEAMMATES).dispatch();
// Load all effort types
new EffortEvent(EffortEvent.LOAD_TYPES).dispatch();
// Load all projects
new ProjectEvent(ProjectEvent.LOAD_PROJECTS).dispatch();
// Load all roles
new TeammateEvent(TeammateEvent.LOAD_TEAMMATE_ROLES).dispatch();
...
Batch Commands
In order to avoid having to dispatch n events for n commands to be executed, I would rather map 1 event to n commands and have them fired off in sequence. Then, if I need a batch of commands to fire, I only need to dispatch one event, instead of the 4 I used in the example above.
new ApplicationEvent(ApplicationEvent.LOAD_MODEL).dispatch();
Unfortunately, Cairngorm does not allow this, so I had to extend the basic architecture. I created a addCommands() function that maps a single event to multiple commands as an array.
public class Controller extends ExtendedFrontController
{
public function Controller()
{
addCommands(ApplicationEvent.LOAD_MODEL, [LoadTeammatesCommand,
LoadEffortTypesCommand,
LoadProjectsCommand,
LoadTeammateRolesCommand,
...]);
}
}
Extended Controller Class
Here’s the code that I’ve come up with so far. This is working fine, but there still some things I plan on implementing in the future.
ExtendedFrontController.as
public class ExtendedFrontController extends FrontController
{
public function ExtendedFrontController()
{
super();
}
public function addCommands(commandName:String, commandRefs:Array, useWeakReference:Boolean = true):void
{
if(commands[ commandName ] != null)
{
throw new CairngormError( CairngormMessageCodes.COMMAND_ALREADY_REGISTERED, commandName );
}
if (commandRefs == null)
{
throw new Error("The commandRefs argument to addCommands() is null");
}
commands[commandName] = commandRefs;
for (var priority:uint = 0; priority < commandRefs.length; priority++)
{
CairngormEventDispatcher.getInstance().addEventListener(commandName, executeCommands, false, priority, useWeakReference);
}
}
protected function executeCommands(event:AbstractEvent):void
{
var commandsToInitialise:Array = getCommands(event.type);
var commandRef:Class;
for (var i:uint = 0; i < commandsToInitialise.length; i++)
{
commandRef = commandsToInitialise[i];
var commandToExecute:ICustomCommand = new commandRef();
commandToExecute.execute(event);
}
}
protected function getCommands(commandName:String):Array
{
var commands:Array = commands[commandName];
if ( commands == null )
throw new CairngormError( CairngormMessageCodes.COMMAND_NOT_FOUND, commandName );
return commands;
}
}


Very useful indeed. I previously used SequenceCommand and nextEvent but this requires to do something special in each command.
Your approach seems to not require any modification to commands.
@Karl: Yes, I did not like the implementation of SequenceCommand because, imo, it broke the basic purpose of the Command pattern. This keeps each Command independent and decoupled from any other logic in the system.
Nice post.
In the following post http://www.awholenewweb.com/?p=51 I share an example in which I extended the events and commands to support Undo operations. I also needed to find a good way to avoid the N event files 1 for each data point in my model but I solved it by making the value object update its own properties.
Your approach seems applicable for many other use cases, thanks for sharing.