みっつめ

enum の使い方(Java)

『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クラスから正しく認識されるようになる。