ActionScript 3 (Flash/AIR): GoF デザインパターン – Command

命令一つ一つをクラスにすることで、まずコードが読みやすくなったり、処理を後で実行したり、ログを取ったり、やり直したり、ロードを待ったりできます。命令をキュー的に実行して、redo / undo の対応もできたりします。

Progression にも SerialList というクラスがありますが、これがまさに Command パターンに基づいています。多分。それと Command パターンの発展系として Thread ライブラリ(そうめん)があります。

ほかにも Command パターンをベースにしたライブラリはいくつかありますが、Command パターンのこの仕組みが便利というよりかは、ポリモーフィズムをコンセプトにしているってことが肝なんだと思います。

ActionScript: Command.as

package jp.feb19.gof.command
{
	public class Command
	{
		public function Command()
		{
		}
		
		public function execute():void
		{
			
		}
		
		public function unexecute():void
		{
			
		}
	}
}

ActionScript: Calculator.as

package jp.feb19.gof.command
{
	public class Calculator	//Receiver
	{
		private var _curr:int = 0;
		
		public function Calculator()
		{
			
		}
		
		public function operation(operator:String, operand:int):void
		{
			switch(operator)
			{
				case "+":	_curr += operand;	break;
				case "-":	_curr -= operand;	break;
				case "*":	_curr *= operand;	break;
				case "/":	_curr /= operand;	break;
			}
			
			trace("current value=" + _curr + " (" + operator + " " + operand + ")");
		}
	}
}

ActionScript: Invoker.as

package jp.feb19.gof.command
{
	public class Invoker
	{
		private var _calculator:Calculator;
		private var _commands:Array;
		private var _current:int;
		
		public function Invoker()
		{
			_calculator = new Calculator();
			_commands = [];
			_current = 0;
		}
		
		public function redo(levels:int):void
		{
			trace(levels + "回リドゥ");
			for (var i:int = 0; i < levels; i++)
			{
				if (_current < _commands.length - 1)
				{
					var command:Command = _commands[_current++];
					command.execute();
				}
			}
		}
		
		public function undo(levels:int):void
		{
			trace(levels + "回アンドゥ");
			for (var i:int = 0; i < levels; i++)
			{
				if (_current > 0)
				{
					var command:Command = _commands[--_current] as Command;
					command.unexecute();
				}
			}
		}
		
		public function compute(operator:String, operand:int):void
		{
			var command:Command = new CalculatorCommand(_calculator, operator, operand);
			command.execute();
			
			_commands.push(command);
			_current++;
		}
	}
}

Command サブクラス。
コマンド?→けいさん 的な。

ActionScript: CalculatorCommand.as

package jp.feb19.gof.command
{
	public class CalculatorCommand extends Command	// ConcreteCommand
	{
		private var _calculator:Calculator;
		private var _operator:String;
		private var _operand:int;
		
		public function CalculatorCommand(calculator:Calculator, operator:String, operand:int)
		{
			_calculator = calculator;
			_operator = operator;
			_operand = operand;
			
			super();
		}
		
		public function set operator(value:String):void
		{
			_operator = value;
		}
		
		public function set operand(value:int):void
		{
			_operand = value;
		}
		
		override public function execute():void
		{
			_calculator.operation(_operator, _operand);
		}
		
		override public function unexecute():void
		{
			_calculator.operation(undo(_operator), _operand);
		}
		
		private function undo(value:String):String
		{
			switch(value)
			{
				case "+":	return "-";
				case "-":	return "+";
				case "*":	return "/";
				case "/":	return "*";
				default:	throw new ArgumentError("演算子エラー: " + value);
			}
		}
	}
}

テストクラス

ActionScript: CommandTest.as

package jp.feb19.gof.command
{
	import flash.display.Sprite;
	
	public class CommandTest extends Sprite
	{
		public function CommandTest()
		{
			super();
			
			var invoker:Invoker = new Invoker();
			
			invoker.compute("+", 100);
			invoker.compute("-", 30);
			invoker.compute("*", 50);
			invoker.compute("/", 4);
			
			invoker.undo(4);
			
			invoker.redo(3);
		}
	}
}

出力

current value=100 (+ 100)
current value=70 (- 30)
current value=3500 (* 50)
current value=875 (/ 4)
4回アンドゥ
current value=3500 (* 4)
current value=70 (/ 50)
current value=100 (+ 30)
current value=0 (- 100)
3回リドゥ
current value=100 (+ 100)
current value=70 (- 30)
current value=3500 (* 50)

非同期処理の連続実行とかは Command パターンでキュー的に実行すると比較的楽なので、いいと思うのですが、何でもかんでもコマンドにしようというのは、結局オーバーヘッドを大きくすることにつながっている部分もありそうなので、便利な所を使っていくのがいいのじゃないかなぁとか思っています。