12
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

C#でシンプルなStatePatternを組んだ

StatePattern

デザインパターンの一角, StatePatternをC#で組んでみました.

Stateパターンを使えば, 呼び出し側のコードでif文/switch-case文を書かなくて良いというメリットがあります.
今回は, 汎用的に使えるState/Contextのクラスを作ってみます.

ソースはこちら

State

public class State
{
    public StateContext Context
    {
        get;
        private set;
    }

    public delegate void stateEnterEvent();
    public stateEnterEvent OnEnter;

    public delegate void stateExitEvent();
    public stateEnterEvent OnExit;

    public State(StateContext context)
    {
        Context = context;
    }
}

ベースとなるステートクラスでは, 「自分のステートに遷移した時」と「自分のステートから出て行った時」のイベントを用意しています.

Context

各Stateを管理して, Stateの遷移を管理・実行するクラスです.

public class StateContext
{
    public List<State> StateList = new List<State>();
    public State CurrentState { get; private set; }

    // Allow transit to self state
    public bool SelfTransit = true;

    object Locker = new object();

    public void setCurrentState(State state)
    {
        if (state == null || !StateList.Contains(state))
            return;

        CurrentState = state;
    }

    public void addState(State state)
    {
        if (state == null || StateList.Contains(state))
            return;
        StateList.Add(state);
    }

    public void transitState(State targetState)
    {
        if (targetState == null || (StateList.Contains(targetState) && SelfTransit))
        {
            return;
        }

        lock (Locker)
        {
            CurrentState?.OnExit();
            CurrentState = targetState;
            CurrentState?.OnEnter();
        }
    }
}

状態の追加には「addState」, 遷移には「transitState」を呼び出せばOKです.

使い方

State/StateContextを継承した自作State/自作Contextを作り, AddStateを使ってStateを登録していきます.
遷移時のイベントは「onEnter」「onExit」に登録可能です.
詳しいコードはGitHubに上げているので, そちらをご覧ください.

一つ一つのStateが大きな場合

↓のように, 具象クラスを別ファイルで実装してみると良いと思います.

LargeState.cs
public class LargeState : State
{
    public LargetState(LargetContext context, LargeState next) : base(context)
    {
         ...          
    }   
}

有効利用できそうなケース

UnityでUI組むときとかに使えそうです.

UIの場合, ユーザからの入力情報は変わらないけど, 階層構造をもっていて画面遷移する事が多いです.
Stateに応じた入力処理・画面遷移を組めば, 呼び出し側のコード(ビヘイビア)は綺麗に保てそうです.

StatePatternを使う上での注意点

呼び出すメソッドが共通化できる時に使わないと, 却ってコードの煩雑化を招きます

例えば, こんなケース.

public class StateA : State
{
     public void myOwnMethodA();
}

public class StateB : State
{
     public void myOwnMethodB();
}

...

public class MyContext : Context
{
     public void onInput()
     {
        if (CurrentState.is(StateA))
        {
           (CurrentState as StateA).myOwnMethodA();
        }
        ...
     }
}

StateAとStateBの間で異なるインタフェースしか提供されないと, 結局Context側で無駄な条件分岐が発生します.

本来やりたかったのは「呼び出し側のコードがif文/switch-case文を使いたくない」という事なので, 本末転倒です.
「あ, このメソッドは共通化できるな」という見通しが立ってから使った方がよさそうです.

(...という認識であってるかなー? ご意見あれば, コメント欄に書き込んで下さると勉強になります)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
12
Help us understand the problem. What are the problem?