亀シミュレータ(Commandパターンバージョン)
それではここから、「平面上を歩きまわる亀のシミュレータ」を題材として、Commandパターンを用いて作られたプログラムが、ラムダ式を使ってどのように書き直せるかを見ていきます。このシミュレータは、ユーザが矢印文字「←↓↑→」からなる文字列を入力することによって、XY座標上にいる「亀」の位置を動かすというものです。例えば、開始時点で(X=0, Y=0)にいる亀に「↓↓→」という入力を与えると、亀は(X=1, Y=2)に移動します。今回のプログラムでは簡単のため、ユーザの入力文字は定数で与えることにします。また、プログラムの最後に移動を終えた亀の位置を標準出力に書き出します。
まずは、Commandパターンを使って実装した場合のプログラムを見てみましょう。このプログラムのクラス図は図3のとおりです(一部のクラスは省略されています)。
それぞれのクラス、インタフェースの役割は次のとおりです。
- Motion:亀の動作を表すコマンドオブジェクトのインタフェース。Commandの役割を担う
- LeftMotion:矢印文字「←」に対応し、亀を左方向に動かすコマンドオブジェクトのクラス。ConcreteCommandの役割を担う
- DownMotion:矢印文字「↓」に対応し、亀を下方向に動かすコマンドオブジェクトのクラス。ConcreteCommandの役割を担う
- UpMotion:矢印文字「↑」に対応し、亀を上方向に動かすコマンドオブジェクトのクラス。ConcreteCommandの役割を担う
- RightMotion:矢印文字「→」に対応し、亀を右方向に動かすコマンドオブジェクトのクラス。ConcreteCommandの役割を担う
- Turtle:操作対象の亀。Receiverの役割を担う
- TurtleMover:矢印文字にしたがって亀の動きを起動するクラス。Invokerの役割を担う
-
CommandTurtleSimulator:矢印文字に対応する亀の動きを
TurtleMoverクラスのインスタンスに登録するクラス。Clientの役割を担う。
また、Commandパターンを用いたプログラムのシーケンス図は図4のとおりです(一部のクラスは省略されています)。
CommandTurtleSimulator クラスは、矢印文字と亀の動きとの対応を、registerMotion メソッドを用いて TurtleMover クラスのインスタンスに登録します。その後、TurtleMover クラスの moveAround メソッドを用いて亀の動きを実行します。
Commandパターンを用いたプログラムをリスト6に掲げます。
import java.util.*;
public class CommandTurtleSimulator {
public static void main(String[] args) {
// (1) ユーザの入力
String userInput = "↓←←↑↑→→→→↓↓";
// (2) 矢印文字に対応する動作を登録
TurtleMover turtleMover = new TurtleMover();
turtleMover.registerMotion('←', new LeftMotion());
turtleMover.registerMotion('↓', new DownMotion());
turtleMover.registerMotion('↑', new UpMotion());
turtleMover.registerMotion('→', new RightMotion());
// (3) 原点上の亀
Turtle turtle = new Turtle(0, 0);
// (4) 入力に従って亀を動かす
turtleMover.moveAround(turtle, userInput);
// (5) 移動先を表示
System.out.printf("X=%d Y=%d%n", turtle.getX(), turtle.getY());
}
/** 平面上の亀. */
static class Turtle {
private int x, y;
Turtle(int x, int y) {
this.x = x;
this.y = y;
}
int getX() {
return this.x;
}
void setX(int x) {
this.x = x;
}
int getY() {
return this.y;
}
void setY(int y) {
this.y = y;
}
}
/** 亀の動きを起動するクラス. */
static class TurtleMover {
/** 文字と動作の対応. */
private final Map<Character, Motion> motionMap = new HashMap<>();
/** 文字に動作を割り当てる. */
void registerMotion(char ch, Motion motion) {
this.motionMap.put(ch, motion);
}
/** 入力文字列に従って亀を動かす. */
void moveAround(Turtle turtle, String input) {
for (char ch : input.toCharArray()) {
Motion motion = this.motionMap.get(ch);
if (motion != null) {
motion.move(turtle);
}
}
}
}
/** 亀の動作. */
interface Motion {
public void move(Turtle turtle);
}
/** 亀を左に動かすコマンド. */
static class LeftMotion implements Motion {
@Override public void move(Turtle turtle) {
turtle.setX(turtle.getX() - 1);
}
}
/** 亀を下に動かすコマンド. */
static class DownMotion implements Motion {
@Override public void move(Turtle turtle) {
turtle.setY(turtle.getY() + 1);
}
}
/** 亀を上に動かすコマンド. */
static class UpMotion implements Motion {
@Override public void move(Turtle turtle) {
turtle.setY(turtle.getY() - 1);
}
}
/** 亀を右に動かすコマンド. */
static class RightMotion implements Motion {
@Override public void move(Turtle turtle) {
turtle.setX(turtle.getX() + 1);
}
}
}
実行結果は次のとおりです。
X=2 Y=1
以上のように、TurtleMover クラスの内部に亀の動作を記述することはなく、具体的な動作は登録されたコマンドオブジェクトに託しています。


