読者です 読者をやめる 読者になる 読者になる

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パターンを使うと、

  • 状態を保持する変数と、その変数による条件分岐での動作の記述、ではなく、
  • 状態を表すクラスを作成し、現在の状態のクラスのインスタンスに動作を依頼する。

という形になる。