Commandパターン


 ★★☆

Commandパターン

要求をオブジェクトにして、それらを組み合わせて使う

Command パターンは、要求を Command オブジェクトにして、それらを複数組み合わせて使えるようにするパターンです。

あるオブジェクトに対して要求を送るということは、そのオブジェクトのメソッドを呼び出すことと同じです。 そして、メソッドにどのような引数を渡すか、ということによって要求の内容は表現されます。さまざまな要求を送ろうとすると、引数の数や種類を増やさなければなりませんが、 それには限界があります。そこで要求自体をオブジェクトにしてしまい、そのオブジェクトを引数に渡すようにします。それが Command パターンです。


例えば、既存の処理方法を組み合わせてデータ処理をしていたとします。処理方法はよく知っていても、それらを組み合わせて行う手順が毎回違うので、その都度処理手順を書き直す必要があります。そこで、処理方法を一つにつき一枚の紙に書き、それらを処理手順通り重ねて束にした処理手順書を作りました。こうすれば、処理の順番が変わっても、処理方法の書かれた紙の順番を変えるだけで済むというわけです。

Command は Event と呼ばれることもあります。「イベント駆動プログラミング」で使われる「イベント」と同じ意味です。マウスをクリックした、キーを押した、といったイベントが起きたときに、その出来事をいったんインスタンスという「もの」にしておき、発生順に処理しておくのです。

GUIアプリケーションなどでは、GUI部品から編集対象のオブジェクトへ命令(コマンド)を送らなければならないため、GUI部品と編集対象のオブジェクトが密な関係になりやすい。しかし、編集対象のオブジェクトの汎用性を高くするためには、GUI部品とは基本的に切り離されていた方が望ましい。 このような問題を解決するには、Command パターンが向いています。


例題

計算過程を記憶する電卓クラスを作成しなさい。(とりあえず、加算と乗算のみ)

後で、最初に与えられた数値を変更しても、記憶している同じ過程で計算ができるようにしなさい。

  (10 + 20) * 4

 次に、10 を 30 に変えて

  (30 + 20) * 4



実行結果
120.0           
200.0








Commandパターンを使用しない例

Operator.java
public class Operator { public static final int ADD = 1; public static final int SUBTRACT = 2; public static final int MULTIPLY = 3; public static final int DIVIDE = 4; protected int operation; protected double value; public Operator(int operation, double value) { this.operation = operation; this.value = value; } }
Calculator.java
public class Calculator { private ArrayList<Operator> operations = new ArrayList<Operator>(); private double value = 0.0; public Calculator(double value) { this.value = value; } public Calculator set(double value) { this.value = value; return this; } public Calculator add(double value) { operationAdd(value); operations.add(new Operator(Operator.ADD, value)); return this; } public Calculator multiply(double value) { operationMultiply(value); operations.add(new Operator(Operator.MULTIPLY, value)); return this; } public void operationAdd(double value) { this.value += value; } public void operationMultiply(double value) { this.value *= value; } public String toString() { return String.format("%.1f", value); } public void clear() { operations.clear(); } public Calculator recalc() { Iterator<Operator> it = operations.iterator(); while(it.hasNext()) { Operator cmd = (Operator)it.next(); switch (cmd.operation) { case Operator.ADD: operationAdd(cmd.value); break; case Operator.MULTIPLY: operationMultiply(cmd.value); break; } } return this; } }
Main.java
public class Main { public static void main(String[] args) { Calculator cal = new Calculator(10); System.out.println(cal.add(20).multiply(4)); System.out.println(cal.set(30).recalc()); } }

Commandパターンを使用した例

AbstractCommand.java
public abstract class AbstractCommand { protected double value; public Calculator cal; public abstract void execute(); }
MacroCommand.java
public class MacroCommand extends AbstractCommand { private ArrayList<AbstractCommand> commands = new ArrayList<AbstractCommand>(); public MacroCommand(Calculator cal) { this.cal = cal; } public void append(AbstractCommand cmd) { if (cmd != this) { cmd.cal = this.cal; this.commands.add(cmd); } } public void clear() { commands.clear(); } public void execute() { Iterator<AbstractCommand> it = commands.iterator(); while(it.hasNext()) { it.next().execute(); } } }
CommandAdd.java
public class CommandAdd extends AbstractCommand { public CommandAdd(double value) { this.value = value; } public void execute() { cal.set(cal.get() + value); } }
CommandMultiply.java
public class CommandMultiply extends AbstractCommand { public CommandMultiply(double value) { this.value = value; } public void execute() { cal.set(cal.get() * value); } }
Calculator.java
public class Calculator { private double value = 0.0; public Calculator(double value) { this.value = value; } public void set(double value) { this.value = value; } public double get() { return value; } public String toString() { return String.format("%.1f", value); } }
Main.java
public class Main { public static void main(String[] args) { Calculator cal = new Calculator(10); MacroCommand com = new MacroCommand(cal); com.append(new CommandAdd(20)); com.append(new CommandMultiply(4)); com.execute(); System.out.println(cal); cal.set(30); com.execute(); System.out.println(cal); } }

この例では、演算ごとに演算の種類とそのときの値(足したり掛けたりする値)を記憶していきます。そして、再計算の時には、記憶されていた演算の種類とそのときの値とを受け取り、計算していきます。計算はうまくできているようです。

しかし、この後減算と除算を追加しようとしたら、recalc() に新たに減算と除算のための case 文を追加しなければなりません。これでは拡張性が良いとは言えません。

Command パターンでは、演算の種類を int 値で表すような方法はやめて、演算そのものを1つのCommand オブジェクトに含ませ、そのオブジェクトごと引数に渡す方法を考えます。演算の種類、つまりCommand オブジェクトに共通のインターフェースを持たせることにより、MacroCommand クラスは、どんな種類の演算(Command オブジェクト)を受け取っても、共通の演算を行うメソッド(execute)を実行すれば良いことになります。

この後減算と除算を追加するのも、Command クラスを継承すればよいだけです。Calculator クラスの execute() メソッドが演算の方法を知っている必要はありません。演算は、Command クラスの各サブクラスに委譲すればよいのです。(ここでも、ポリモーフィズムが利用されています。)


value に 10 という値を持った Calculator インスタンスを持つ MacroCommand インスタンスを生成します。

次に、value に 20 という値と Calculator インスタンスを持った CommandAdd インスタンスをスタックに保存します。(Calculator インスタンスはすべて同じもの)

次に、value に 4 という値と Calculator インスタンスを持った CommandMultiply インスタンスをスタックに保存します。(Calculator インスタンスはすべて同じもの)

そして、MacroCommand インスタンスの execute() で計算します。

まず、最初のオブジェクトは CommandAdd インスタンスなので、CommandAdd インスタンスの execute() を呼び出します。Calculator インスタンスの value (=10)と自身の value (=20)を加算(=30)し、Calculator インスタンスの value にセットします。

そして、次のオブジェクトは CommandMultiply インスタンスなので、CommandMultiply インスタンスの execute() を呼び出します。Calculator インスタンスの value (=30)と自身の value (=4)を乗算(=120)し、Calculator インスタンスの value にセットします。

計算が終われば、Calculator インスタンスの value(=120) を表示します。


Command パターンを適用すると、各演算のソースコードを変更しなくても、いろいろな演算を追加することができます。また、既存の演算を組み合わせて、新たな演算を作ることも可能です。新しい演算の execute メソッド内に、既存の演算の execute メソッドを記述すれば、新しい演算が実行された際、記述した順に既存の演算も実行されます。これにより、再利用性も高くなります。