デザインパターン Stateについて
Stateパターンとは
ステートパターンは状態をクラスとして表現して、クラスを切り替えることで状態の変化を表すことができます。
サンプルプログラム
状態をクラスとして表現する例として、ATM使用時に時刻ごとに手数料が異なるシステムを考えてみましょう。 以下を時刻ごとの手数料として、画面に表示するとします。
9:00~17:59 ・手数料が0円
18:00~8:59 ・手数料が200円
これを単純にif文で表現すると以下のようになります。
ATM使用時に呼ばれるメソッド () { if (9:00~17:59) { 手数料0円を画面表示 } else if (18:00~8:59) { 手数料200円を画面表示 } }
Stateパターンを使わない場合は、状態のチェックにif文を使用します。 Stateパターンではif文を使用しません。 9:00~17:59と18:00~8:59のATMの状態をクラスとして表現します。 作成したサンプルプログラムを見てみましょう。
・サンプルプログラムで登場するクラスとインターフェース
Stateインターフェース
ATMの状態を表しています。 引数のContextで状態の管理を行っており、 useATMメソッドは状態に応じて、処理が変化するメソッドです。
public interface State { public abstract void doClock(Context context, int hour); // 時刻設定 public abstract void useATM(Context context); // ATM使用 }
DayState1クラス
DayState1クラスは9:00~17:59の状態を表すクラスです。 doClockメソッド内で、状態の遷移をしています。 また、インスタンスは一つしか作れないように、Singletonパターンを使用しています。
public class DayState1 implements State { private static DayState1 singleton = new DayState1(); private DayState1() { // コンストラクタはprivate } public static State getInstance() { // 唯一のインスタンスを得る return singleton; } public void doClock(Context context, int hour) { // 時刻設定 if (hour < 9 || 18 <= hour) { context.changeState(DayState2.getInstance()); } } public void useATM(Context context) { // ATM使用 context.showScreen("手数料は0円です。"); } }
DayState2クラス
DayState2クラスは18:00~8:59の状態を表すクラスです。 DayState1と同様にdoClockメソッド内で、状態の遷移をしています。
public class DayState2 implements State { private static DayState2 singleton = new DayState2(); private DayState2() { // コンストラクタはprivate } public static State getInstance() { // 唯一のインスタンスを得る return singleton; } public void doClock(Context context, int hour) { // 時刻設定 if (9 <= hour && hour < 18) { context.changeState(DayState1.getInstance()); } } public void useATM(Context context) { // ATM使用 context.showScreen("手数料は200円です。"); } }
Contextクラス
状態の管理をしているインターフェースです。
public interface Context { public abstract void setClock(int hour); // 時刻設定 public abstract void changeState(State state); // 状態変化 public abstract void showScreen(String msg); // 手数料表示 public abstract void recordLog(String msg); // 記録 }
ATMFrameクラス
GUIを使ってATMシステムを実現するクラスです。 コンストラクタで背景色やボタンの配置を決めています。 ここで注目すべきは、ATM使用ボタンを押したときに呼ばれる、useATMメソッドです。 if文で時刻の状態チェックをするわけでなく、いきなりメソッドを呼び出しています。 ここでは現在の状態を表すクラスをchangeStateメソッドに引数で渡し、 状態を表すフィールドに設定することで、遷移を行っています。
public class ATMFrame extends Frame implements ActionListener, Context { private TextField textClock = new TextField(60); // 現在時刻表示 private TextArea textScreen = new TextArea(10, 60); // ATM出力 private Button buttonUse = new Button("ATM使用"); // ATM使用ボタン private Button buttonExit = new Button("終了"); // 終了ボタン private State state = DayState1.getInstance(); // 現在の状態 // コンストラクタ public ATMFrame(String title) { super(title); setBackground(Color.lightGray); setLayout(new BorderLayout()); // textClockを配置 add(textClock, BorderLayout.NORTH); textClock.setEditable(false); // textScreenを配置 add(textScreen, BorderLayout.CENTER); textScreen.setEditable(false); // パネルにボタンを格納 Panel panel = new Panel(); panel.add(buttonUse); panel.add(buttonExit); // そのパネルを配置 add(panel, BorderLayout.SOUTH); // 表示 pack(); show(); // リスナーの設定 buttonUse.addActionListener(this); buttonExit.addActionListener(this); } // ボタンが押されたらここに来る public void actionPerformed(ActionEvent e) { System.out.println(e.toString()); if (e.getSource() == buttonUse) { // ATM使用ボタン state.useATM(this); } else if (e.getSource() == buttonExit) { //終了ボタン System.exit(0); } else { System.out.println("?"); } } // 時刻の設定 public void setClock(int hour) { String clockstring = "現在時刻は"; if (hour < 10) { clockstring += "0" + hour + ":00"; } else { clockstring += hour + ":00"; } System.out.println(clockstring); textClock.setText(clockstring); state.doClock(this, hour); } // 状態変化 public void changeState(State state) { System.out.println(this.state + "から" + state + "へ状態が変化しました。"); this.state = state; } // ATMの画面に手数料を出力 public void showScreen(String msg) { textScreen.append(msg + "\n"); } // ATM記録 public void recordLog(String msg) { textScreen.append("record ... " + msg + "\n"); } }
Mainクラス
時刻の設定を1時間ごとに行っています。
public class Main { public static void main(String[] args) { ATMFrame frame = new ATMFrame("State Sample"); while (true) { for (int hour = 0; hour < 24; hour++) { frame.setClock(hour); try { Thread.sleep(1000); } catch (InterruptedException e) { } } } } }
実行結果
18:00~8:59にATM使用ボタン押下
9:00~17:59にATM使用ボタン押下
まとめ
システムの各状態を個別のクラスで表現するStateパターンを学びました。 Stateパターンで押さえておくべきポイントは以下の2点です。
・抽象メソッドとして宣言し、インターフェースとする
・具象メソッドとして実装し、個々のクラスとする
これがStateパターンの状態に依存した処理の表現方法となります。