『Javaで学ぶリファクタリング入門』の p.225〜p.227 にかけて、以下のような if文での処理をクラスによる処理に書き直す例が出ている。
public void executeCommand( Command command )
throws InvalidCommandException {
if (command == Command.FORWARD) {
// 処理
} else if (command == Command.BACKWARD) {
// 処理
} else if (command == Command.TURN_RIGHT) {
// 処理
} else if (command == Command.TURN_LEFT) {
// 処理
} else {
throw new InvalidCommandException();
}
}
これを、次のようにクラスにして処理を分けてた。
public class Command {
public static final Command FORWARD = new Command( "forward" );
public static final Command BACKWARD = new Command( "backward" );
public static final Command TURN_RIGHT = new Command( "right" );
public static final Command TURN_LEFT = new Command( "left" );
これを、匿名クラスを使って以下のようにしていた。
public abstract class Command {
public static final Command FORWARD = new Command("forward") {
@Override
public void execute (Robot robot) { robot.forward(); }
};
public static final Command BACKWARD = new Command("backward") {
@Override
public void execute (Robot robot) { robot.backward(); }
};
public static final Command TURN_RIGHT = new Command("right") {
@Override
public void execute (Robot robot) { robot.right(); }
};
public static final Command TURN_LEFT = new Command("left") {
@Override
public void execute (Robot robot) { robot.left(); }
};
で、ここからが本題なんだけど、これを enum を使って書かれへんかあということで、やってみた。
で、できたのが、以下。
Command.java
import java.util.HashMap;
public enum Command implements CommandExecute {
FORWARD ("forward"){ // <1>
@Override
public void execute (Robot robot) { robot.forward(); // <2>
},
BACKWARD ("backward"){
@Override
public void execute (Robot robot) { robot.backward(); }
},
TURN_RIGHT ("right"){
@Override
public void execute (Robot robot) { robot.right(); }
},
TURN_LEFT ("left"){
@Override
public void execute (Robot robot) { robot.left(); }
};
private String name; // <3>
private Command (String name) { this.name = name; } // <4>
public String getName() { return this.name; } // <5>
private static final HashMap<String, Command> _commandNameMap =
new HashMap <> ();
static {
_commandNameMap.put( Command.FORWARD.getName(), FORWARD ); // <6>
_commandNameMap.put( Command.BACKWARD.getName(), BACKWARD );
_commandNameMap.put( Command.TURN_RIGHT.getName(), TURN_RIGHT );
_commandNameMap.put( Command.TURN_LEFT.getName(), TURN_LEFT );
}
public static Command parseCommand (String name)
throws InvalidCommandException {
if (! _commandNameMap.containsKey( name )) { // <7>
throw new InvalidCommandException( name );
}
return _commandNameMap.get( name ); // <8>
}
}
CommandExecute.java // インターフェース
public interface CommandExecute {
public void execute( Robot robot );
}
まず、enum として、FORWARD, BACKWARD, TURN_RIGHT, TURN_LEFT の4つを定義する。
public enum Command {
FORWARD,
BACKWARD,
TURN_RIGHT,
TURN_LEFT;
}
それぞれに別名をつけることができる。
public enum Command {
FORWARD ("forward"),
BACKWARD ("backward"),
TURN_RIGHT ("right"),
TURN_LEFT ("left");
private String name; // <3>
private Command (String name) { this.name = name; } // <4>
public String getName() { return this.name; } // <5>
}
不思議な記述だけど、これでうまく動いている。
<3> で、nameという変数を設定して、 <4> のコンストラクタで、その変数name に値をセットしている。
そして、<5> のgetName()メソッドで、その name を返している。
enum には、name() というメソッドがあるが、Command.FORWARD.name() で、FORWARD が返る。
FORWARD ("forward"){ // <1>
@Override
public void execute (Robot robot) { robot.forward(); } // <2>
},
ここでは、メソッドを定義している。enum では抽象クラスを extends できないので、インターフェースを使っている。
private static final HashMap<String, Command> _commandNameMap =
new HashMap <> ();
static {
_commandNameMap.put( Command.FORWARD.getName(), FORWARD );
_commandNameMap.put( Command.BACKWARD.getName(), BACKWARD );
_commandNameMap.put( Command.TURN_RIGHT.getName(), TURN_RIGHT );
_commandNameMap.put( Command.TURN_LEFT.getName(), TURN_LEFT );
}
<6> では、Command.FORWARD.getName() で、”forward” という文字列を得ることができる。
だから、_commandNameMap.put( “forward”, FORWARD ) ということになる。
<7> で、_commandNameMap に name が含まれているかをチェックし、含まれていたら、<8> で、name に対応する値を取得し、返している。
この enum については、別のクラスから以下のような形で利用している。
public class Robot {
// (省略)
Command command = Command.parseCommand( commandString ); // <9>
command.execute( this ); // <10>
// (省略)
}
この commandString には、”forward” や “right” などの文字が与えられている。
<9> で、その commandString が 本当に正しいか、含まれているかをチェックし、もし、含まれていたら、その enum Command を command にセットしている。
そして <10> で enum Command の executeメソッドを実行している。
this というのは、このクラス、すなわち、 Robotインスタンスのことである。
execute は、enum Command が implements CommandExecute をすることでRobotクラスから正しく認識されるようになる。