An Embedded Engineer’s Blog

とある組み込みエンジニアの備忘録的なブログです。

UMLのステートマシン図を実装する for C# - その3

まえがき

今回は、前回説明したサンプルのエアコンステートマシンを実装していきます。

f:id:an-embedded-engineer:20190414174321p:plain
エアコンステートマシン


StateMachine クラス

StateMachine クラスを継承したステートマシンを実装します。
エアコンステートマシンでは、3 つのステートマシンを実装する必要があります。

名称 詳細 備考
ModelStateMachine エアコン全体のステートマシン
RunningStateMachine Running 状態のサブステートマシン
CleanStateMachine Clean 状態のサブステートマシン


ModelStateMachine クラス

ModelStateMachine クラスは、エアコンモデルの AirConditioner クラス(後述)を引数として受け取るコンストラクタを定義します。
コンストラクタ内では、各状態(State)からモデルの処理を呼び出し可能にするため、エアコンモデルを Public プロパティにセットします。
その後、StateMachine クラスで定義された ChangeToInitalState メソッドを呼び出すことで初期状態への遷移を開始します。
また、継承元の StateMachine クラスで宣言された抽象メソッドの GetInitialState を定義し、ステートマシンの初期状態を返すようにします。


public class ModelStateMachine : StateMachine
{
    // エアコンモデル
    public AirConditioner Model { get; }

    // コンストラクタ
    public ModelStateMachine(AirConditioner model)
    {
        this.Model = model;

        this.ChangeToInitialState();
    }

    // 初期状態取得
    protected override State GetInitialState()
    {
        return InitialState.Instance;
    }
}


RunningStateMachine クラス

ModelStateMachine クラスはサブステートマシンなので、親である ModelStateMachine クラスを引数として受け取るコンストラクタを定義します。
コンストラクタでは、受け取った ModelStateMachine クラス、およびそこから取得できるエアコンモデル(AirConditioner クラス)を Public プロパティにセットします。 あとは ModelStateMachine クラスと同様、ChangeToInitialState メソッドの呼び出しと、GetInitialState メソッドの定義を行います。
RunningStateMachine の初期状態は、前回説明したとおり、Cool 状態となります。 (システム初期化後初めて Running 状態に遷移した場合は、履歴が残されていないため、Cool 状態に遷移する)

public class RunningStateMachine : StateMachine
{
    // 親ステートマシン
    public ModelStateMachine Parent { get; }

    // エアコンモデル
    public AirConditioner Model { get; }

    // コンストラクタ
    public RunningStateMachine(ModelStateMachine parent)
    {
        this.Parent = parent;

        this.Model = parent.Model;

        this.ChangeToInitialState();
    }

    // 初期状態取得
    protected override State GetInitialState()
    {
        return CoolState.Instance;
    }
}


CleanStateMachine クラス

CleanStateMachine クラスも RunningStateMachine と同様に定義します。 CleanStateMachine の初期状態は StainLevelAnalysis 状態となります。

public class CleanStateMachine : StateMachine
{
    // 親ステートマシン
    public ModelStateMachine Parent { get; }

    // エアコンモデル
    public AirConditioner Model { get; }

    // コンストラクタ
    public CleanStateMachine(ModelStateMachine parent)
    {
        this.Parent = parent;

        this.Model = parent.Model;

        this.ChangeToInitialState();
    }

    // 初期状態取得
    protected override State GetInitialState()
    {
        return StainLevelAnalysisState.Instance;
    }
}


Trigger クラス

Trigger クラスを継承した各トリガを実装します。
エアコンステートマシンでは、7 つのトリガを実装する必要があります。

名称 詳細 備考
InitializedTrigger 初期化完了トリガ
SwitchStartTrigger スタートボタン押下トリガ
SwitchStopTrigger ストップボタン押下トリガ
SwitchCoolTrigger 冷房ボタン押下トリガ
SwitchHeatTrigger 暖房ボタン押下トリガ
SwitchDryTrigger 除湿ボタン押下トリガ
SwitchCleanTrigger クリーニングボタン押下トリガ


各トリガはシステム内で 1 つのインスタンスのみを持つべきなので、シングルトンで実装します。
また、状態遷移時にエフェクトを実行する必要がある場合は、Effect クラスを継承したエフェクトをベースクラスのコンストラクタに受け渡します。

InitializedTrigger クラス

public sealed class InitializedTrigger : Trigger
{
    public static InitializedTrigger Instance { get; private set; } = new InitializedTrigger();

    public InitializedTrigger() : base("Initialized Trigger")
    {
    }
}


SwitchStartTrigger クラス

public sealed class SwitchStartTrigger : Trigger
{
    // シングルトンインスタンス
    public static SwitchStartTrigger Instance { get; private set; } = new SwitchStartTrigger();

    // コンストラクタ
    public SwitchStartTrigger() : base("Switch Start Trigger", SwitchStartEffect.Instance)
    {
    }
}


SwitchStopTrigger クラス

public sealed class SwitchStopTrigger : Trigger
{
    // シングルトンインスタンス
    public static SwitchStopTrigger Instance { get; private set; } = new SwitchStopTrigger();

    // コンストラクタ
    public SwitchStopTrigger() : base("Switch Stop Trigger", SwitchStopEffect.Instance)
    {
    }
}


SwitchCoolTrigger クラス

public sealed class SwitchCoolTrigger : Trigger
{
    // シングルトンインスタンス
    public static SwitchCoolTrigger Instance { get; private set; } = new SwitchCoolTrigger();

    // コンストラクタ
    public SwitchCoolTrigger() : base("Switch Cool Trigger")
    {
    }
}


SwitchHeatTrigger クラス

public sealed class SwitchHeatTrigger : Trigger
{
    // シングルトンインスタンス
    public static SwitchHeatTrigger Instance { get; private set; } = new SwitchHeatTrigger();

    // コンストラクタ
    public SwitchHeatTrigger() : base("Switch Heat Trigger")
    {
    }
}


SwitchDryTrigger クラス

public sealed class SwitchDryTrigger : Trigger
{
    // シングルトンインスタンス
    public static SwitchDryTrigger Instance { get; private set; } = new SwitchDryTrigger();

    // コンストラクタ
    public SwitchDryTrigger() : base("Switch Dry Trigger")
    {
    }
}


SwitchCleanTrigger クラス

public sealed class SwitchCleanTrigger : Trigger
{
    // シングルトンインスタンス
    public static SwitchCleanTrigger Instance { get; private set; } = new SwitchCleanTrigger();

    // コンストラクタ
    public SwitchCleanTrigger() : base("Switch Clean Trigger")
    {
    }
}


Effect クラス

Effect クラスを継承した各エフェクトを実装します。
エアコンステートマシンでは、3 つのエフェクトを実装する必要があります。

名称 詳細 備考
SwitchStartEffect スタートボタン押下エフェクト
SwitchStopEffect ストップボタン押下エフェクト
CleanEndEffect クリーニング完了エフェクト


各エフェクトはシステム内で 1 つのインスタンスのみを持つべきなので、シングルトンで実装します。
また、ベースクラスのEffectクラスで宣言された抽象メソッドExecuteActionをオーバーライドすることで、エフェクト固有の動作を定義します。

SwitchStartEffect クラス

SwitchStartEffectクラスでは、スタートボタンが押下され、Running状態に遷移する際に行われる処理を定義します。

protected override void ExecuteAction(StateMachine context)
{
    // 親ステートマシン(ModelStateMachine)を取得
    var stm = context.GetAs<ModelStateMachine>();

    // モデル(AirConditioner)を取得
    var model = stm.Model;

    // エアコンのスタート処理を呼び出し
    model.Start();
}


ソース全体を示します。

public sealed class SwitchStartEffect : Effect
{
    // シングルトンインスタンス
    public static SwitchStartEffect Instance { get; private set; } = new SwitchStartEffect();

    // コンストラクタ
    public SwitchStartEffect() : base("Switch Start Effect")
    {
    }

    // エフェクトアクション
    protected override void ExecuteAction(StateMachine context)
    {
        var stm = context.GetAs<ModelStateMachine>();

        var model = stm.Model;

        model.Start();
    }
}


SwitchStopEffect クラス

SwitchStoptEffectクラスでは、ストップボタンが押下され、Stop状態に遷移する際に行われる処理を定義します。

protected override void ExecuteAction(StateMachine context)
{
    // 親ステートマシン(ModelStateMachine)を取得
    var stm = context.GetAs<ModelStateMachine>();

    // モデル(AirConditioner)を取得
    var model = stm.Model;

    // エアコンの停止処理を呼び出し
    model.Stop();
}


ソース全体を示します。

public sealed class SwitchStopEffect : Effect
{
    // シングルトンインスタンス
    public static SwitchStopEffect Instance { get; private set; } = new SwitchStopEffect();

    // コンストラクタ
    public SwitchStopEffect() : base("Switch Stop Effect")
    {
    }

    // エフェクトアクション
    protected override void ExecuteAction(StateMachine context)
    {
        var stm = context.GetAs<ModelStateMachine>();

        var model = stm.Model;

        model.Stop();
    }
}


CleanEndEffect クラス

CleanEndEffectでは、クリーニング処理が完了し、Running状態に遷移する際に行われる処理を定義します。

// エフェクトアクション
protected override void ExecuteAction(StateMachine context)
{
    // 親ステートマシン(ModelStateMachine)を取得
    var stm = context.GetAs<ModelStateMachine>();

    // モデル(AirConditioner)を取得
    var model = stm.Model;

    // エアコンのクリーニング完了処理を呼び出し
    model.CleanEnd();
}


ソース全体を示します。

public sealed class CleanEndEffect : Effect
{
    // シングルトンインスタンス
    public static CleanEndEffect Instance { get; private set; } = new CleanEndEffect();

    // コンストラクタ
    public CleanEndEffect() : base("Clean End Effect")
    {
    }

    // エフェクトアクション
    protected override void ExecuteAction(StateMachine context)
    {
        var stm = context.GetAs<ModelStateMachine>();

        var model = stm.Model;

        model.CleanEnd();
    }
}


次回予告

次回も引き続きステートマシンの実装を行っていきます。

an-embedded-engineer.hateblo.jp

UMLのステートマシン図を実装する for C# - その2

まえがき

今回は、前回作成したベースクラスを使って実装するステートマシンのサンプルについて説明します。

f:id:an-embedded-engineer:20190414174321p:plain
エアコンステートマシン


上記ステートマシンは、エアコンの状態遷移を表したものになります。
なお、今回作成するステートマシンはサンプルのため、現実のエアコンの挙動とは異なる場合があります。

エアコンステートマシンは大きく 3 つの状態に別れます。

状態 詳細 備考
Initial 初期状態
Stop エアコン停止状態
Running エアコン稼働状態
Clean エアコンクリーニング状態


Initial 状態

Initial 状態はステートマシン開始前の初期状態(疑似状態)です。
システム起動後、各種初期化が完了すると、「T_Initialized」トリガが発行され、Stop 状態に遷移します。


Stop 状態

Stop 状態は、エアコンが停止し、ユーザからの要求を待機している状態です。
ユーザがリモコンのスタートボタンを押すと、「T_SwitchStart」トリガが発行され、Running 状態に遷移します。
また、状態遷移時に「E_SwitchStartEffect」エフェクトが実行されます。


Running 状態

Running 状態は、エアコンが通常動作している状態です。 今回作成するエアコンでは、冷房/暖房/除湿の動作モードがあり、それぞれユーザのリモコン操作で切り替えることができるものとします。

Running 状態でユーザがストップボタンを押すと「T_SwitchStop」トリガが発行され、Stop 状態に遷移します。
また、状態遷移時に「E_SwitchStopEffect」エフェクトが実行されます。


Running 状態でユーザが内部クリーニングボタンを押すと「T_SwitchClean」トリガが発行され、Clean 状態に遷移します。


Running 状態でユーザが各種モード切り替えボタンを押すことでサブ状態が変化し、動作モードを変更することができます。

Running 状態のサブ状態を示します。

状態 詳細 備考
History 履歴疑似状態
Cool 冷房状態
Heat 暖房状態
Dry 除湿状態


History サブ状態

前回 Running 状態を出た時のサブ状態を記憶しておくための疑似状態です。
Running 状態への初回遷移時には、履歴が存在しないため Cool 状態に遷移します。


Cool サブ状態

Cool サブ状態は、冷房制御を行っている状態です。
指定された目標温度になるように室内の温度制御を行います。


Cool サブ状態でユーザが暖房ボタンを押すと「T_SwitchHeat」トリガが発行され、Heat サブ状態に遷移します。


Cool サブ状態でユーザが除湿ボタンを押すと「T_SwitchDry」トリガが発行され、Dry サブ状態に遷移します。


Heat サブ状態

Heat サブ状態は、暖房制御を行っている状態です。
指定された目標温度になるように室内の温度制御を行います。


Heat サブ状態でユーザが冷房ボタンを押すと「T_SwitchCool」トリガが発行され、Cool サブ状態に遷移します。


Heat サブ状態でユーザが除湿ボタンを押すと「T_SwitchDry」トリガが発行され、Dry サブ状態に遷移します。


Dry サブ状態

Dry サブ状態は、除湿制御を行っている状態です。
湿度が下がるように室内の湿度制御を行います。


Dry サブ状態でユーザが冷房ボタンを押すと「T_SwitchCool」トリガが発行され、Cool サブ状態に遷移します。


Dry サブ状態でユーザが暖房ボタンを押すと「T_SwitchHeat」トリガが発行され、Heat サブ状態に遷移します。


Clean 状態

Clean 状態は、エアコンが内部クリーニングをしている状態です。 今回作成するエアコンでは、内部の汚れレベル(Low / High)を自動的に判断し、レベルに応じてクリーニングモード(入念 / あっさり)を自動的に切り替えます。

Running 状態でユーザがストップボタンを押すと「T_SwitchStop」トリガが発行され、Stop 状態に遷移します。
また、状態遷移時に「E_SwitchStopEffect」エフェクトが実行されます。


クリーニング処理が完了すると、自動的に Running 状態に遷移します。
また、状態遷移時に「E_CleanEndEffect」エフェクトが実行されます。


Clean 状態に入ると、汚れレベルの判定およびクリーニング処理が自動的に行われ、その際にサブ状態も自動的に遷移します。


Clean 状態のサブ状態を示します。

状態 詳細 備考
Initial 開始疑似状態
StainLevelAnalysis 汚れレベル解析状態
DeepClean 入念クリーニング状態
LightClean あっさりクリーニング状態
Final 最終状態


Initial サブ状態

Inital サブ状態は Clean 状態におけるサブステートマシン開始前の初期状態(疑似状態)です。
Clean 状態に入ると、サブ状態は自動的に StainLevelAnalysis サブ状態に遷移します。


StainLevelAnalysis サブ状態

StainLevelAnalysis サブ状態は、汚れレベルの解析を行っている状態です。 汚れレベルの解析が終了すると、解析された汚れレベルに応じて、自動的に状態遷移が発生します。

汚れレベル(Stain Level)が High だった場合、DeepClean サブ状態に遷移します。
また、汚れレベルが Low だった場合は LightClean サブ状態に遷移します。


DeepClean サブ状態

DeepClean サブ状態は、入念クリーニングを行っている状態です。 クリーニング処理が終了すると、Final 状態に自動的に遷移します。


LightClean サブ状態

LightClean サブ状態は、あっさりクリーニングを行っている状態です。 クリーニング処理が終了すると、Final 状態に自動的に遷移します。


Final サブ状態

Final サブ状態は、クリーニング処理が完了した状態です。 Final サブ状態に遷移すると、Clean 状態は自動的に Running 状態に遷移します。


次回予告

次回はいよいよステートマシンを実装していきます。

an-embedded-engineer.hateblo.jp

UMLのステートマシン図を実装する for C# - その1

まえがき

ステートマシン(状態機械)の実装方法にはいくつか方法があり、有名なものとして GoF デザインパターンState パターンがありますが、UML で表記されるステートマシン図の実装方法について書かれた Web ページなどをあまり見かけないので、State パターンをベースに実装してみました。

ベースクラス定義

ステートマシンを実装する上での共通処理をベースクラスとして定義します。

クラス名 役割 備考
StateMachine トリガの送信、状態遷移の管理
State 状態ごとの動作、トリガに対する動作の制御
Trigger 状態遷移を発生させるためのトリガ
Effect 状態遷移時に実行されるアクション


StateMachine クラス

StateMachine クラスはステートマシンそのものを表すクラスで、トリガの送信や状態遷移の管理を行います。
StateMachine クラスは抽象クラスとして定義します。

public abstract class StateMachine
{
}


プロパティとしては、現在の状態(CurrentState)と、ひとつ前の状態(PreviousState)を保持します。

// 現在の状態
public State CurrentState { get; private set; }

// ひとつ前の状態
public State PreviousState { get; private set; }


ステートマシン開始後の初期状態を設定するためのメソッドを定義します。
初期状態として何を設定したいかは、ステートマシンに依存するので、初期状態を取得する抽象メソッドと、 取得した初期状態に遷移させるためのメソッドを定義します。

// 初期状態の取得
protected abstract State GetInitialState();

// 初期状態への遷移
protected void ChangeToInitialState()
{
    var initial_state = this.GetInitialState();

    this.ChangeState(initial_state);
}


続いて、トリガの送信です。
ステートマシンは、トリガの発生によって状態遷移が発生します。
本実装では、ステートマシンが外部からのトリガを受信すると、 現在の状態(CurrentState)に対してトリガを送信します。
CurrentState は受信したトリガに応じて、アクションの実行や状態遷移を発生させます。

// トリガの送信
public void SendTrigger(Trigger trigger)
{
    this.CurrentState?.SendTrigger(this, trigger);
}


次に、状態遷移処理です。
UML のステートマシンは、以下のような手続きに従って状態遷移を行います。

  1. 現在の状態における Exit アクションの実行(定義されている場合)
  2. 状態を変更
  3. エフェクト処理を実行(定義されている場合) *1
  4. 新しい状態における Entry アクションの実行(定義されている場合)
// 状態遷移
public void ChangeState(State new_state, Effect effect = null)
{
    if (this.CurrentState != new_state)
    {
        var old_state = this.CurrentState;

        this.CurrentState?.ExecuteExitAction(this);

        this.CurrentState = new_state;
        this.PreviousState = old_state;

        effect?.Execute(this);

        this.CurrentState?.ExecuteEntryAction(this);
    }
}


各状態における Do アクションは、その状態であり続ける限り定期的に実行されます。
外部から Do アクションを実行するための Update メソッドを定義します。

// 更新
public void Update()
{
    this.CurrentState?.ExecuteDoAction(this);
}


最後に StateMachine クラスを継承先のサブクラスにキャストするためのメソッドを定義します。

public T GetAs<T>() where T : StateMachine
{
    if (this is T stm)
    {
        return stm;
    }
    else
    {
        throw new InvalidOperationException($"State Machine is not {nameof(T)}");
    }
}


ソース全体を以下に示します。

public abstract class StateMachine
{
    public State CurrentState { get; private set; }

    public State PreviousState { get; private set; }

    protected abstract State GetInitialState();

    public StateMachine()
    {
    }

    public void SendTrigger(Trigger trigger)
    {
        this.CurrentState?.SendTrigger(this, trigger);
    }

    public void ChangeState(State new_state, Effect effect = null)
    {
        if (this.CurrentState != new_state)
        {
            var old_state = this.CurrentState;

            this.CurrentState?.ExecuteExitAction(this);

            this.CurrentState = new_state;
            this.PreviousState = old_state;

            effect?.Execute(this);

            this.CurrentState?.ExecuteEntryAction(this);
        }
    }

    public void Update()
    {
        this.CurrentState?.ExecuteDoAction(this);
    }

    public T GetAs<T>() where T : StateMachine
    {
        if (this is T stm)
        {
            return stm;
        }
        else
        {
            throw new InvalidOperationException($"State Machine is not {nameof(T)}");
        }
    }

    protected void ChangeToInitialState()
    {
        var initial_state = this.GetInitialState();

        this.ChangeState(initial_state);
    }
}


StateEventHandler デリゲート

StateEventHandler デリゲートは、State(状態)に定義されたイベント(Entry / Do / Exit)の呼び出しを行うためのデリゲートです。

StateMachine を引数として受け取るデリゲートとして定義します。

public delegate void StateEventHandler(StateMachine conetxt);


TriggerActionMap クラス

TriggerActionMap クラスは、Trigger 受診時に実行するアクションを登録するハッシュテーブルです。
文字列を Key、TriggerActionArgs を引数にとる Action を Value とした Dictionary クラスを継承します。

public class TriggerActionMap : Dictionary<string, Action<TriggerActionArgs>>
{
}


TriggerActionArgs クラス

TriggerActionArgs クラスは、Trigger 受信に実行されるアクションに渡される引数です。
State の親となる StateMachine と、受信した Trigger を含みます。

public class TriggerActionArgs
{
    public StateMachine Context { get; }

    public Trigger Trigger { get; }

    public TriggerActionArgs(StateMachine context, Trigger trigger)
    {
        this.Context = context;

        this.Trigger = trigger;
    }
}


Trigger クラス

Trigger クラスは、状態遷移を発生させるトリガを表すクラスです。
Trigger クラスは抽象クラスとして定義し、Trigger 名と(必要に応じて)Effect を保持します。

public abstract class Trigger
{
    public string Name { get; }

    public Effect Effect { get; }

    public Trigger(string name, Effect effect = null)
    {
        this.Name = name;

        this.Effect = effect;
    }
}


Effect クラス

Effect クラスは、状態遷移時に実行されるエフェクトを表すクラスです。
Effect クラスは抽象クラスとして定義します。

プロパティとしては Effect 名(Name)を保持します。

また、エフェクトで実行するアクション(ExecuteAction)を抽象メソッドとして定義し、継承先で実装させます。

最後に、StateMachine か Effect を実行するためのパブリックメソッド(Execute)を定義します。

public abstract class Effect
{
    string Name { get; }

    protected abstract void ExecuteAction(StateMachine context);

    public Effect(string name)
    {
        this.Name = name;
    }

    public void Execute(StateMachine context)
    {
        this.ExecuteAction(context);
    }
}


State クラス

State クラスはステートマシンの1つの状態を表すクラスで、 状態ごとのイベント(Entry / Do / Exit)やトリガに対するアクションを定義します。
State クラスは抽象クラスとして定義します。

public abstract class State
{
}


プロパティとしては、以下の情報を保持します。

プロパティ名 役割 備考
Name string 状態名
OnEntry StateEventHandler Entry イベント処理
OnDo StateEventHandler Do イベント処理
OnExit StateEventHandler Exit イベント処理
TriggerActionMap TriggerActionMap Trigger 受信時に実行するアクションのハッシュテーブル


public string Name { get; }

protected StateEventHandler OnEntry;
protected StateEventHandler OnDo;
protected StateEventHandler OnExit;

private TriggerActionMap TriggerActionMap { get; set; }


Trigger を受信した際の動作は状態ごとに異なるため、TriggerActionMap の生成を継承先で行うための抽象メソッド(GenerateTriggerActionMap)を定義し、コンストラクタで実行します。

protected abstract TriggerActionMap GenerateTriggerActionMap();

public State(string name)
{
    this.Name = name;

    this.TriggerActionMap = this.GenerateTriggerActionMap();
}


続いて、状態ごとのイベント(Entry / Do / Exit)を StateMachine から実行させるためのパブリックメソッドを定義します。
それぞれのイベントにイベントハンドラが定義されていたら、そのイベントハンドラを呼び出すようにします。

public void ExecuteEntryAction(StateMachine context)
{
    this.OnEntry?.Invoke(context);
}

public void ExecuteDoAction(StateMachine context)
{
    this.OnDo?.Invoke(context);
}

public void ExecuteExitAction(StateMachine context)
{
    this.OnExit?.Invoke(context);
}


最後に、状態に対して Trigger が送信された時の動作を定義します。
TriggerActionMap に送信された Trigger の名前が登録されていたら、Trigger に対応する Action を取得します。
StateMacine と Trigger を TriggerActionArgs にセットし、それを引数として Action を実行します。

public void SendTrigger(StateMachine context, Trigger trigger)
{
    if (this.TriggerActionMap.ContainsKey(trigger.Name) == true)
    {
        var action = this.TriggerActionMap[trigger.Name];

        var args = new TriggerActionArgs(context, trigger);

        action(args);
    }
}


ソース全体を以下に示します。

public abstract class State
{
    public string Name { get; }

    protected StateEventHandler OnEntry;
    protected StateEventHandler OnDo;
    protected StateEventHandler OnExit;

    private TriggerActionMap TriggerActionMap { get; set; }

    protected abstract TriggerActionMap GenerateTriggerActionMap();

    public State(string name)
    {
        this.Name = name;

        this.TriggerActionMap = this.GenerateTriggerActionMap();
    }

    public void ExecuteEntryAction(StateMachine context)
    {
        this.OnEntry?.Invoke(context);
    }

    public void ExecuteDoAction(StateMachine context)
    {
        this.OnDo?.Invoke(context);
    }

    public void ExecuteExitAction(StateMachine context)
    {
        this.OnExit?.Invoke(context);
    }

    public void SendTrigger(StateMachine context, Trigger trigger)
    {
        if (this.TriggerActionMap.ContainsKey(trigger.Name) == true)
        {
            var action = this.TriggerActionMap[trigger.Name];

            var args = new TriggerActionArgs(context, trigger);

            action(args);
        }
    }
}


次回予告

次回は、今回作成したベースクラスを使って実装する、サンプルのステートマシンについて説明します。

an-embedded-engineer.hateblo.jp

*1:UML の仕様では、エフェクト実行時の状態は『不定』となっていますが、本実装では状態変更後にしています