The Command Pattern is particularily useful in ActionScript projects because of the asynchronous nature of it's event driven workings. Most of the time we don't really have control of what will happen or in what order.
To handle this easily, we pack the different operations in discrete small objects that all share a simple interface (normally, just requiring an execute() method). And those objects can be later called by other processes that need not know anything about how they work.
You can pass these command objects around, store them in arrays, etc., without having to know their inner mechanics, only what they are for and that whenever you need them to do their thing (however it is that they do it) you just need to call their execute() method.
In most design pattern books you see examples of commands expanded with undo() methods, nested up in Macros (commands that aggregate other commands in them, so that when the execute method is called on that command, it'll sequentially run the execute on all of its children commands), and you'll see mentioned that these can also be arranged in queues and other hierarchical configurations.
Here you can download a simple pure ActionScript (no Flex or Cairngorm this time!) command package that has the basic command and macro functionality, as well as a couple of other nice things that I couldn't find anywhere else, so I had to brew them myself: An Abstract Command that'll dispatch event for when it's complete or if it failed for some reason. A Macro Command, based on this, that will agreggate several other commands and run them all in one blow. And the most useful and beautiful CommandQueue, for which you define a sequence of commands that will be run one at a time, waiting for one command to finish (or fail) before continuing (or aborting) the rest of the queue.
This CommandQueue is particularily useful for things like booting up an application, where you want to have a clear view of what is happening and in what order. With this command you can easily read, switch and shuffle the small operations that make up the booting up (or whatever other deferred operation you need to control). Check out this usage example extracted from a real app:
package com.example {
import com.example.boot.*;
import com.rojored.command.CommandQueue;
import flash.display.Sprite;
import flash.events.ErrorEvent;
import flash.events.Event;
public class ExampleApp {
private static var _instance :ExampleApp;
// ------- access point to the application -------
public function init(canvas :Sprite,
configUrl :String = "xml/config.xml") :void {
// create boot sequence
var bootSequence :CommandQueue = new CommandQueue();
// add commands to the boot sequence queue
bootSequence.addCommand(
new LoadSettingsCommand(configUrl),
new SetStagePropertiesCommand(canvas),
new CreateHoldersCommand(canvas),
new CreateFPSWidgetCommand(),
new InitCursorManagerCommand(),
new LoadBackgroundCommand(),
new BuildFooterCommand(),
new BuildHeaderCommand(),
new InitPanelManagerCommand(),
new LoadPanelsCommand(),
new AnimateInCommand(),
new ActivatePanelManagerCommand()
);
// setup event listeners for the boot queue result
bootSequence.addEventListener(
Event.COMPLETE,
onBootComplete
);
bootSequence.addEventListener(
ErrorEvent.ERROR,
onBootComplete
);
// intialize bootup
bootSequence.execute();
}
// ------- bootup result handler -------
private function onBootComplete(event :Event) :void {
// cleanup
event.target.removeEventListener(
Event.COMPLETE,
onBootComplete
);
event.target.removeEventListener(
ErrorEvent.ERROR,
onBootComplete
);
// result
switch(event.type) {
case Event.COMPLETE:
trace("BOOT: boot complete");
break;
case ErrorEvent.ERROR:
trace("BOOT FAIL: " + (event as ErrorEvent).text);
break;
}
}
// ------- constructor and accessor -------
public function ExampleApp(enforcer :SingletonEnforcer) {}
public static function getInstance() :ExampleApp {
if (_instance == null) {
_instance = new ExampleApp(new SingletonEnforcer());
}
return _instance;
}
}
}
class SingletonEnforcer {}
None of the commands added should need to know anything about the previous commands or anything about what will happen after they are done. (No loading the xml on the handler for the settings loaded event!). Here's one of the classes referenced in the previous example:
package com.example.boot {
import com.example.PanelEvent;
import com.example.PanelManager;
import com.rojored.command.AbstractCommand;
public class AnimateInCommand extends AbstractCommand {
override public function execute() :void {
trace("BOOT: intro animation");
var panelManager :PanelManager = PanelManager.getInstance();
panelManager.addEventListener(
PanelEvent.INTRO_TWEEN_COMPLETE,
onAnimateIn
);
panelManager.animateIn();
}
private function onAnimateIn(event :PanelEvent) :void {
PanelManager.getInstance().removeEventListener(
PanelEvent.INTRO_TWEEN_COMPLETE,
onAnimateIn
);
onCommandComplete();
}
}
}
Notice how we call the onCommandComplete() method of the abstract command. That alone will automatically handle all the event dispatching that, in turn, will be used by the command queue to fire the next event in line. In this example, it is the ActivatePanelManagerCommand, which is in now way coupled with the process of "animating in" from the panel manager, its previous stage in the bootup process.
If this command could fail in some way (like a command that loads external assets could, for example), we would call, instead of the onCommandComplete() method, the onCommandFail() method, which would signal the command queue that that particular command didn't go through. The queue could, depending on value of its abortOnFail flag (passed as paramet on its constructor) stop the train and abort the process. This would also dispatch an event that we could use for displaying and error or whatever we needed to run to get back on track.