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();
...
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,
...]);
}
}
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.
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;
}
}
6 Responses for "Cairngorm Patterns: Batch 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.
Excellent article, Steve! I found it very useful. However, I had a question, a little tangential maybe. I have this scenario: I have several commands, each loading a set of table contents into an app from a SQLite DB. I want to batch these commands together during the app start. However, I need one or more of these commands to fire (not just execute, but provide information (respond immediately) to the next command through either result/fault). I was wondering how I could achieve that. I am using an async connection to the DB. Is it just that I use Sync connections or can something be done to listen to callbacks before executing the next command?
Thanks!
Yes, you can chain the commands. Create a ChainCommands class that takes an invoker (Event) as a constructor argument, and also an array of command classes.
Then create the following methods:
execute() – This starts the command execution process by calling executeCommand()
executeCommand() – Performs the execution of the each command in the array
executeNextCommand() – This increments the internal counter to keep track of which command you are executing, captures any data from the current execution, and then calls executeCommand() again, forming the loop.
Thanks for the prompt reply, Steve. I am not sure how I would be able to capture data returned by the command, executing currently, before passing it on to the next?
Leave a reply