Stateパターン
Stateパターンは、オブジェクトの状態によって動作を変えるときに使う。
例として文字列の引用符 (") の対応をチェックしてみる。ここで、オブジェクトの状態は、
- 引用符の外
- 引用符の中
の二つ。それぞれの状態での動作は、
- 引用符の外状態
- 次の文字が引用符なら、引用符の中状態に遷移
- この状態で終了なら結果は OK
- 引用符の中状態
- 次の文字が引用符なら、引用符の外状態に遷移
- この状態で終了なら結果は、引用符の対応が取れていないのでエラー
状態を表すクラスは、
- ParserState -- 状態を表す抽象クラス。それぞれの状態での動作を抽象メソッドとして宣言する。
- OutOfQuoteState, InQuoteState -- 引用符の外と中を表す具象クラス。それぞれの状態に応じてメソッドの中身を実装する。
abstract class ParserState { internal abstract void ParseChar(char c, QuoteParser parser); internal abstract bool GetResult(); } class OutOfQuoteState : ParserState { internal static readonly OutOfQuoteState Instance = new OutOfQuoteState(); private OutOfQuoteState() { } internal override void ParseChar(char c, QuoteParser parser) { // 引用符の*外*状態で引用符 => 引用符の*中*状態に遷移する if (c == '"') { parser.TransitionTo(InQuoteState.Instance); } } internal override bool GetResult() { // 引用符の*外*ならば OK return true; } } class InQuoteState : ParserState { internal static readonly InQuoteState Instance = new InQuoteState(); private InQuoteState() { } internal override void ParseChar(char c, QuoteParser parser) { // 引用符の*中*状態で引用符 => 引用符の*外*状態に遷移する if (c == '"') { parser.TransitionTo(OutOfQuoteState.Instance); } } internal override bool GetResult() { // 引用符の*中*ならば、引用符の対応が取れていない。 return false; } }
OutOfQuoteStateとInQuoteStateは、メンバ変数がなくインスタンスは一つあればいいので、Singletonにしている。
指定の文字列の引用符の対応チェックを担当するQuoteParserクラスは、
- Parseメソッドで、保持する状態を用いて、文字を解析し、結果を取得する。
- TransitionToメソッドで指定の状態に遷移する。
class QuoteParser { private ParserState m_state; // 指定の文字列の引用符の対応が取れているか解析する。 internal bool Parse(string str) { // 引用符の*外*状態で開始。 m_state = OutOfQuoteState.Instance; // 現在の状態を用いて、文字を解析し、結果を取得する。 foreach (char c in str) { m_state.ParseChar(c, this); } return m_state.GetResult(); } // 指定の状態に遷移する。 internal void TransitionTo(ParserState state) { m_state = state; } }
QuoteParserクラスを使うプログラムは、
class Program { static void Main(string[] args) { Parse("\"対応している\""); Parse("\"対応\"していない\""); } private static void Parse(String str) { var parser = new QuoteParser(); bool result = parser.Parse(str); Console.WriteLine("{0} => {1}", str, result); } // ===== 画面への出力 ===== // "対応している" => True // "対応"していない" => False }
Stateパターンを使うと、
- 状態を保持する変数と、その変数による条件分岐での動作の記述、ではなく、
- 状態を表すクラスを作成し、現在の状態のクラスのインスタンスに動作を依頼する。
という形になる。