Command パターンでリクエストのキューイングを実装する

MovieClip.loadMovie や XML.load など、非同期のリクエストを正しい順序で行うには Command パターンとキューを使うと良さそう。……というわけで、簡単な Command クラスと CommandQueue クラスを書いてみた。CommandQueue に Command インスタンスを enqueue で詰め込んでから、dequeue して一連のコマンド (リクエスト) を実行する仕組み。

まずは、Command クラス。インターフェイスにしたいところだけど、EventDispatcher を使う関係で、コンストラクタを private にした抽象クラスにした。実行メソッドを execute としている。

import mx.events.EventDispatcher;
/**
*  コマンド
*/
class Command {
public var addEventListener:Function;
private var dispatchEvent:Function;
public var execute:Function;
/**
*  コンストラクタ
*  @param command コマンドオブジェクト
*/
private function Command(command:Command) {
EventDispatcher.initialize(command);
}
}

次に、上記 Command クラスを継承した HogeCommand クラス。execute が呼び出されるとタイマーが作動して、止まったら onComplete イベントを発行する。

/**
*  ほげコマンド
*/
class HogeCommand extends Command {
private var time:Number = 0;
private var timerID:Number = null;
private var message:String;
/**
*  コンストラクタ
*  @param time タイマーの時間
*  @param message トレースメッセージ
*/
public function HogeCommand(time:Number, message:String) {
super(this);
this.time = time;
this.message = message;
}
/**
*  実行
*/
public function execute():Void {
if (this.timerID == null) {
this.timerID = setInterval(this, "hoge", this.time);
}
}
/**
*  タイマー動作
*/
private function hoge():Void {
trace("hogeCommand complete : " + this.message);
clearInterval(this.timerID);
this.dispatchEvent({type:"onComplete"});
}
}

最後に、CommandQueue クラス。enqueue で リストに Command を追加して、dequeue で Command を順番に実行する。リストが空になると onEmpty イベントを発行する。

import mx.utils.Delegate;
import mx.events.EventDispatcher;
/**
*  コマンドキュー
*/
class CommandQueue {
public var addEventListener:Function;
private var dispatchEvent:Function;
private var commands:Array;
/**
*  コンストラクタ
*/
public function CommandQueue() {
EventDispatcher.initialize(this);
this.commands = new Array();
}
/**
*  キューにコマンドを追加
*  @param command 追加するコマンド
*/
public function enqueue(command:Command):Void {
this.commands.push(command);
}
/**
*  キューからコマンドを取り出して実行
*  ※リストが空になるまで再帰的に実行する
*/
public function dequeue():Void {
if (this.commands.length > 0) {
var nextCommand:Command = Command(this.commands.shift());
nextCommand.addEventListener("onComplete", Delegate.create(this, dequeue));
nextCommand.execute();
} else {
this.dispatchEvent({type:"onEmpty"});
}
}
}

そして、いよいよ実行!

import CommandQueue;
import HogeCommand;
//インスタンスを作成
var commandQueue:CommandQueue = new CommandQueue();
var hoge1:HogeCommand = new HogeCommand(2000, "hoge1");
var hoge2:HogeCommand = new HogeCommand(1000, "hoge2");
//キューのリスナを作成
var commandQueueListener:Object = new Object();
commandQueueListener.onEmpty = function():Void {
trace("commandQueue is empty !");
};
commandQueue.addEventListener("onEmpty", commandQueueListener);
//実行!
commandQueue.enqueue(hoge1);
commandQueue.enqueue(hoge2);
commandQueue.dequeue();

以下の結果を得られる。

hogeCommand complete : hoge1
hogeCommand complete : hoge2
commandQueue is empty !

hoge1 の終了を待ってから hoge2 を実行するところがポイントになる。HogeCommand ではタイマーで擬似的に遅延を発生させたけれど、最初に書いた MovieClip.loadMovie や XML.load を Command パターンで実装するには、LoadMovieCommand 等のラッパークラスを作って、execute から loadMovie を行うようにする。そして onLoad イベントが呼び出されたら onComplete イベントを発行すればよい。