デザインパターンstateについて勉強した

stateパターンとは

stateは状態という意味です。
現在の状態をクラスとして考えるパターンをstateパターンといいます。

例として菊池さんの1日を考えてみます。
菊池さんは5〜8時は新聞配達、その後18時まで会社員、24時までアルバイトをしているとします。
1日をコードで表すとき、どのように書けばいいでしょうか。
まずはstateパターンでない書き方を見てみます。

public void greeting(int clock){    //greeting=挨拶
	if(5<=clock && clock<=7) {
		System.out.println("新聞配達です");
	}else if(8<=clock && clock<=17){
		System.out.println("会社員です");
	}else if(18<=clock && clock<=23) {
		System.out.println("アルバイトです");
	}
}

public void work(int clock){
	if(5<=clock && clock<=7) {
		System.out.println("新聞を配達しています");
	}else if(8<=clock && clock<=17){
		System.out.println("プログラムを書いています");
	}else if(18<=clock && clock<=23) {
		System.out.println("レジを打っています");
	}
}

間違いではありませんが、半分近くが同じコードになっています。
これにさらにいくつかメソッドが増えると、どんどんコード量だけが膨れ上がっていきます。
さらに、もしアルバイトの時間を21時までに変更したら、すべてのメソッドを調べて条件を変更することになります。
一つでも見落とせばバグになります。

コード例

今度はstateパターンを見てみます。

State インタフェース

public interface State {

	void greeting();

	void work();
}

「状態」に相当するインタフェースです。
これを実装して「新聞配達員」「会社員」「アルバイト」などの状態を作っていきます。

NewspaperDeliveryクラス

public class NewspaperDelivery implements State {

	@Override
	public void greeting() {
		System.out.println("新聞配達員の菊池です!おはようございます!");
	}

	@Override
	public void work() {
		System.out.println("新聞をポストに投函します。");
	}
}

新聞配達員の状態を表すクラスです。
greetingメソッドもworkメソッドも新聞配達員専用の実装がされています。
何時から何時までのような情報もなく、ただシンプルに仕事内容だけが書かれています。

SystemEngineersクラス

public class SystemEngineers implements State {

	@Override
	public void greeting() {
		System.out.println("システムエンジニアの菊池です。おはようございます。");
	}

	@Override
	public void work() {
		System.out.println("エクセルにスクショを貼り付けています。");
	}
}

日中はシステムエンジニアとして働いています。
「日中は」といいましたが、このクラスにも新聞配達同様に時間の情報は含まれていません。

PartTimeクラス

public class PartTime implements State {

	@Override
	public void greeting() {
		System.out.println("コンビニバイトの菊池です。いらっしゃいませ");
	}

	@Override
	public void work() {
		System.out.println("レジ打ちをします");
	}
}

PartTimeはアルバイトのことです。

Sleepクラス

public class Sleep implements State {

	@Override
	public void greeting() {
		System.out.println("菊池は仕事を終えました。おやすみなさい");
	}

	@Override
	public void work() {
		System.out.println("...zzz");
	}
}

仕事が終わればやっと眠ることができます。

Statemanクラス

public class Stateman {

	private State state ;

	public void setState(State state) {
		if(this.state!=null && this.state.getClass()==state.getClass()) return;

		this.state = state;

		state.greeting();
	}

	public void setClock(int clock) {
		if(5<=clock && clock<=7) {
			setState(new NewspaperDelivery());
		}else if(8<=clock && clock<=17){
			setState(new SystemEngineers());
		}else if(18<=clock && clock<=23) {
			setState(new PartTime());
		}else {
			setState(new Sleep());
		}
	}

	public Stateman() {
		for(int c=5;;c=(c+1)%24) {
			setClock(c);
			System.out.print(c+":00\t");
			state.work();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public static void main(String[] args) {
		new Stateman();
	}
}

メインとなるクラスです。

stateフィールドは現在の状態を保持するフィールドです。
setStateメソッドはstateフィールドに保持されているクラス型が同じ場合は何もしません。
異なる場合はstateフィールドを変更して菊池さんが挨拶します。

setClockメソッドは現在時刻を渡します。
渡された時刻によって現在の状態を「新聞配達員」「システムエンジニア」「アルバイト」「睡眠」の4つから選択しています。

コンストラクタではforによる無限ループが行われています。
変数iには0~23の数字が順に入れられます。
setClockメソッドに時間を入力し、stateフィールドのworkメソッドを呼び出しています。

実行

新聞配達員の菊池です!おはようございます!
5:00	新聞をポストに投函します。
6:00	新聞をポストに投函します。
7:00	新聞をポストに投函します。
システムエンジニアの菊池です。おはようございます。
8:00	エクセルにスクショを貼り付けています。
9:00	エクセルにスクショを貼り付けています。
10:00	エクセルにスクショを貼り付けています。
11:00	エクセルにスクショを貼り付けています。
12:00	エクセルにスクショを貼り付けています。
13:00	エクセルにスクショを貼り付けています。
14:00	エクセルにスクショを貼り付けています。
15:00	エクセルにスクショを貼り付けています。
16:00	エクセルにスクショを貼り付けています。
17:00	エクセルにスクショを貼り付けています。
コンビニバイトの菊池です。いらっしゃいませ
18:00	レジ打ちをします
19:00	レジ打ちをします
20:00	レジ打ちをします
21:00	レジ打ちをします
22:00	レジ打ちをします
23:00	レジ打ちをします
菊池は仕事を終えました。おやすみなさい
0:00	...zzz
1:00	...zzz
2:00	...zzz
3:00	...zzz
4:00	...zzz
新聞配達員の菊池です!おはようございます!
5:00	新聞をポストに投函します。

特徴

プログラム全体を見て、時間による分岐をしているのがsetClockメソッド内ただ一つであることを確認してください。
また、各状態(各仕事)のクラスにはそれに関係のある仕事しか書かれていないことにも注目してください。
これらにより新聞配達クラスを見れば新聞配達中は何をすべきかがひと目でわかり、どの時間にはどの仕事をすべきかがすぐにわかります。
アルバイトを21時に終わるように変更することも簡単ですし(setClockメソッドのif条件を1文字変更するだけです)、アルバイトの後に新たに「警備員」の仕事を追加することだってどうすればいいかすぐにわかります。(警備員クラスを作り、setClockメソッドにちょこっと追記するだけです)

まとめ

Stateインタフェースの実装クラスを、その仕事について書かれたマニュアルである、
setClockに書かれた時間が来たらそのマニュアルを手元に置いて仕事をする、
というように考えるとイメージしやすいのではないかと思います。