状態遷移型コードのライブラリ

状態遷移型のコードを書くライブラリを使っています。

以下に置いてあります。
https://github.com/kumokumocc/WORK_/tree/master/classlib/cs/Status

自分は、作るコードほとんどこれ使ってます。
使わないと、動作が不安定になって生産性が半分くらいに落ちると思います。

使い方は結構簡単。
StatusCtrlのインスタンスを作って、最初のStatusを引数にしてStartすればOK。
コンストラクタの引数に入れるTextBoxは、現在の状態をリアルタイムに表示してくれるので便利です。

すこし解説が必要なのはStatusクラスですね。
状態１個に対して１個ステータスクラスを用意します。Initialize,OnMoveStatus,OnEvent等から、必要なものをオーバーライドして使う形です。
継承したクラスはたとえばこんな感じで書きます。






    /// <summary>
    /// 停止状態。初期状態です。
    /// この状態でのみ、スタートボタンがEnableになります。
    /// </summary>

    class Status_Stop : StatusBase
    {
        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="testform"></param>
        public Status_Stop(TestForm testform)
            : base(testform)
        {
        }

        /// <summary>
        /// 初期化処理　スタートボタンをEnableします。
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();
            Form.button_Start.Enabled = true;
            
        }

        /// <summary>
        /// 別ステータスに遷移する際の処理　スタートボタンをDisableします。
        /// </summary>
        /// <param name="NextStatus"></param>
        public override void OnMoveStatus(Status NextStatus)
        {
            base.OnMoveStatus(NextStatus);
            Form.button_Start.Enabled = false;
        }


        /// <summary>
        /// イベント時の処理
        /// </summary>
        /// <param name="event_"></param>
        /// <returns></returns>
        public override Status OnEvent(Event event_)
        {
            Status next = base.OnEvent(event_);
            //上位側に従う
            if (next != this)
            {
                return next;
            }

            if (event_ is Event_.Start)
            {
                //スタートボタン押下時の処理

                //型変換
                Event_.Start e = (Event_.Start)event_;
                if (e.IsStart)
                {
                    next = new Status_Start(Form);
                }
            }

            return next;
        }
    }

Initializeは、ステータスに遷移してきた場合に呼ばれます。
OnMoveStatusは、別のステータスに遷移する前に呼ばれます。

つまり、事前処理をInitializeに書いて、事後処理をOnMoveStatusに書きます。
ボタン押下待ち状態だとすると、InitializeでボタンをEnable、OnMoveStatusでDisableにするのが定番です。

OnEventはイベントを受け取ったときの動作です。
他からStatusCtrlのSetEventメソッドを呼び出しすと、送ったイベントを引数とするOnEventが呼ばれる構成になっています。
戻り値が設定されていて、基本的にはthisが返ります。thisが返る間は状態は遷移しません。

this以外のStatusをnewで作って返すと、returnしたStatusに次遷移します。
遷移する前にOnMoveStatusが呼ばれ、次のStatusのInitializeが呼ばれてイベントを待ちます。

イベントはEventクラスを継承して自分で作ります。
データ受信、ボタン押下、タイマータイムアウトといったイベントが定番です。
is演算子でクラスの型を確認して、狙ったイベントだったら詳しいデータを受け取って、、という使い方をします。

>Status next = base.OnEvent(event_);
if (next != this)
{
     return next;
}

最初のここは、上位側で別のステータスに遷移した場合はそれに従うようにします。
このような記述にしておくと、中間クラスを上手く使えます。

中間クラスはたとえば以下のように使います。

https://github.com/kumokumocc/WORK_/blob/master/classlib/cs/Status/Test/StatusBase.cs

    /// <summary>
    /// ベースになるクラス。
    /// どんな状況でも受け入れる処理を記述します。
    /// </summary>
    class StatusBase : Status
    {


        TestForm m_TestForm;
        public TestForm Form
        {
            get
            {
                return m_TestForm;
            }
        }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="testform"></param>
        protected StatusBase(TestForm testform)
        {
            m_TestForm = testform;
            m_Timer = new System.Windows.Forms.Timer();
            if (Interval > 0)
            {
                m_Timer.Interval = Interval;
            }
            m_Timer.Tick += new System.EventHandler(this.timer_Tick);
        }

        /// <summary>
        /// 初期化処理　タイマーをEnableします。
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();
            if (Interval > 0)
            {
                m_Timer.Interval = Interval;
                m_Timer.Enabled = true;
            }
        }

        /// <summary>
        /// ステータス遷移時の処理　タイマーをDisableします。
        /// </summary>
        /// <param name="NextStatus"></param>
        public override void OnMoveStatus(Status NextStatus)
        {
            base.OnMoveStatus(NextStatus);
            m_Timer.Enabled = false;
        }
        /// <summary>
        /// イベント時の処理
        /// </summary>
        /// <param name="event_"></param>
        /// <returns>次のステータスを返します。</returns>
        public override Status OnEvent(Event event_)
        {
            Status next = base.OnEvent(event_);
            //上位側でステータス変更していた場合はそれに従う。
            if (next != this)
            {
                return next;
            }

            if (event_ is Event_.ForceStop)
            {
                //Stop状態に遷移
                next = new Status_Stop(Form);
            }

            if (event_ is Event_.TimeOut)
            {
                //タイムアウト時の処理呼び出し
                next = OnTimeOut();
            }

            return next;
        }

        private System.Windows.Forms.Timer m_Timer;

        /// <summary>
        /// タイマーインターバルを返します。
        /// </summary>
        public virtual int Interval
        {
            get
            {
                return 0;
            }
        }

        /// <summary>
        /// タイマータイムアウト時の処理　タイムアウトイベントをセットします。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void timer_Tick(object sender, EventArgs e)
        {
            Form.StatusCtrl.SetEvent(new Event_.TimeOut());
        }

        /// <summary>
        /// タイムアウト時の処理
        /// </summary>
        /// <returns>次のステータス</returns>
        public virtual Status OnTimeOut()
        {
            return this;
        }






すると、StatusBaseクラスでどんな状態でも受け入れるイベントを記述できます。
例えば、リセットボタンを押された時の動作や、タイマータイムアウトイベント、ヘルスチェックへのAck等がこの処理です。

以下にテストコードもあります。気になった方はどうぞ。
https://github.com/kumokumocc/WORK_/tree/master/classlib/cs/Status/Test

他にも、状態遷移を使って、ソケットの制御やっているライブラリがあります。
一番面倒で失敗しやすいところが全部ブラックボックスになっているので、簡単に通信テスト用のソフトが作れます。
次回はこれを紹介してみます。