暇殺シ

忙しくはない

状態遷移の実装(Java編)

他の人が状態機械Javaでどうやって実装してるのかな~っと調べてみたら条件分岐の嵐だった。オブジェクト指向とはなんだったのか。
そこで私がいつも使っている方法を紹介します。ただ、あらゆるケースに使えるわけではありません。

  • Enum(Java5以降)が使えること。
  • マルチプロセス環境では使えない。

制約はこんなところです。

結論としては、状態をEnum値、状態遷移をメソッドで定義する。それだけです。
まずは簡単な例から。
f:id:Idios:20120707230317p:plain
これは初期状態と受容状態だけを定義した簡単な状態機械図です。
これをクラスに変換すると下記のようになります。状態はEnum値、状態遷移はメソッドで定義しています。

/**
 * Every values defines Machine state and transition.
 * 
 * @author Idios
 * 
 */
enum State {

	/**
	 * Initial state
	 */
	INITIAL {
		@Override
		public State accept() {
			return ACCEPTED;
		}
	},

	/**
	 * Accepted state
	 */
	ACCEPTED

	;

	/**
	 * To accept machine.
	 * 
	 * @return {@value #ACCEPTED}
	 */
	public State accept() {
		throw new IllegalStateException();
	}

}

public class Main {

	private State current = State.INITIAL;

	private void start() {
		current = current.accept();
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		Main machine = new Main();
		machine.start();
		System.out.println("State machine is " + machine.current);
	}

}

状態とその遷移の定義はStateクラス、状態遷移の管理はMainクラスで行う構造になっています。
遷移はINITIALからACCEPTEDのみ定義されているので、INITIALブロックにだけaccept()メソッドを宣言しています。もし、受容状態(ACCEPTED)の時にaccept()を呼び出すと、そんな遷移は定義していないので実行時例外になります。

状態の定義(RUNNING)を追加してみます。
f:id:Idios:20120707231643p:plain
これは初期状態からいきなり受容状態に遷移することがあれば、初期状態からRUNNING状態を経由して受容状態に遷移することもある状態機械を表しています。
そこで、初期状態からRUNNINGに至る遷移run()を定義しています。受容状態に至る遷移はaccept()のみのままです。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Every values defines Machine state and transition.
 * 
 * @author Idios
 * 
 */
enum State {

	/**
	 * Initial state
	 */
	INITIAL {
		@Override
		public State run() {
			return RUNNING;
		}

		@Override
		public State accept() {
			return ACCEPTED;
		}
	},

	RUNNING {
		@Override
		public State accept() {
			return ACCEPTED;
		}
	},

	/**
	 * Accepted state
	 */
	ACCEPTED

	;

	/**
	 * To run the machine
	 * 
	 * @return
	 */
	public State run() {
		throw new IllegalStateException();
	}

	/**
	 * To accept the machine.
	 * 
	 * @return {@value #ACCEPTED}
	 */
	public State accept() {
		throw new IllegalStateException();
	}

}

public class Main {

	private State current = State.INITIAL;

	private void start() {
		current = current.accept();
	}

	private void start(final String string) {
		ExecutorService service = Executors.newFixedThreadPool(1);
		current = current.run();
		Thread runnable = new Thread() {
			@Override
			public void run() {
				// do something
			}
		};
		service.execute(runnable);
		try {
			runnable.join();
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		} finally {
			service.shutdown();
			current = current.accept();
		}
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		Main machine = new Main();
		if (args.length > 0) {
			machine.start(args[0]);
		} else {
			machine.start();
		}
		System.out.println("State machine is " + machine.current);
	}

}

このように、状態はEnum値、状態遷移はメソッドに定義することで割とシンプルに状態機械を実装できるのではないでしょうか。
また、定義されていない遷移が発生すると、ちゃんとチェックされるところが個人的なお気に入りポイントです。

状態遷移表を作ってあればソースコードの生成もできます。

なお、Java編以外の記載予定はありません。