Java Enumを使用した単純なステートマシンの実装

1. 概要

このチュートリアルでは、ステートマシンと、Enumを使用してそれらをJavaで実装する方法について説明します。
また、各状態にインターフェイスと具体的なクラスを使用する場合と比較した、この実装の利点についても説明します。

2. Java列挙

link:/a-guide-to-java-enums[Java Enum]は、定数のリストを定義するクラスの特別なタイプです。 これにより、*タイプセーフな実装とより読みやすいコード*が可能になります。
例として、従業員から提出された休暇申請を承認できるHRソフトウェアシステムがあるとします。 この要求はチームリーダーによって確認され、チームリーダーはそれを部門マネージャーにエスカレートします。 部門マネージャーは、リクエストを承認する責任者です。
休暇申請の状態を保持する最も単純な列挙型は次のとおりです。
public enum LeaveRequestState {
    Submitted,
    Escalated,
    Approved
}
この列挙型の定数を参照できます。
LeaveRequestState state = LeaveRequestState.Submitted;
*列挙型にはメソッドを含めることもできます。*列挙型で抽象メソッドを記述し、すべての列挙型インスタンスにこのメソッドを実装させることができます。 以下に示すように、これはステートマシンの実装にとって非常に重要です。
Java列挙型は_java.lang.Enum_クラスを暗黙的に拡張するため、別のクラスを拡張することはできません。 ただし、他のクラスと同様に、インターフェイスを実装できます。
抽象メソッドを含む列挙型の例を次に示します。
public enum LeaveRequestState {
    Submitted {
        @Override
        public String responsiblePerson() {
            return "Employee";
        }
    },
    Escalated {
        @Override
        public String responsiblePerson() {
            return "Team Leader";
        }
    },
    Approved {
        @Override
        public String responsiblePerson() {
            return "Department Manager";
        }
    };

    public abstract String responsiblePerson();
}
最後の列挙定数の末尾にセミコロンが使用されていることに注意してください。 定数の後に1つ以上のメソッドがある場合、セミコロンが必要です。
この場合、最初の例をa__responsiblePerson()_メソッドで拡張しました。 これにより、各アクションを実行する責任者がわかります。 したがって、_Escalated_状態の責任者を確認しようとすると、「チームリーダー」が得られます。
LeaveRequestState state = LeaveRequestState.Escalated;
assertEquals("Team Leader", state.responsiblePerson());
同様に、リクエストの承認責任者を確認すると、「Department Manager」が表示されます。
LeaveRequestState state = LeaveRequestState.Approved;
assertEquals("Department Manager", state.responsiblePerson());

3. ステートマシン

ステートマシン(有限状態マシンまたは有限オートマトンとも呼ばれます)は、抽象マシンの構築に使用される計算モデルです。 *これらのマシンは、一度に1つの状態にしかなれません。*各状態は、別の状態に変化するシステムのステータスです。 これらの状態変化は遷移と呼ばれます。
数学では図や表記法で複雑になりますが、プログラマーにとってはずっと簡単です。
link:/java-state-design-pattern[State Pattern]は、GoFのよく知られた23種類のデザインパターンの1つです。 このパターンは、数学のモデルから概念を借用しています。 *オブジェクトは、その状態に基づいて、同じオブジェクトのさまざまな動作をカプセル化できます。 状態間の遷移をプログラムし、後で個別の状態を定義できます。*
概念をよりよく説明するために、ステートマシンを実装するために休暇申請の例を拡張します。

4. ステートマシンとしての列挙

Javaのステートマシンの列挙型実装に焦点を当てます。 link:/java-state-design-pattern [その他の実装]が可能です。次のセクションでそれらを比較します。
列挙型を使用したステートマシンの実装の主なポイントは、*状態を明示的に設定する必要がない*ことです。 代わりに、ある状態から次の状態に遷移する方法に関するロジックを提供するだけです。 すぐに飛び込みましょう:
public enum LeaveRequestState {

    Submitted {
        @Override
        public LeaveRequestState nextState() {
            return Escalated;
        }

        @Override
        public String responsiblePerson() {
            return "Employee";
        }
    },
    Escalated {
        @Override
        public LeaveRequestState nextState() {
            return Approved;
        }

        @Override
        public String responsiblePerson() {
            return "Team Leader";
        }
    },
    Approved {
        @Override
        public LeaveRequestState nextState() {
            return this;
        }

        @Override
        public String responsiblePerson() {
            return "Department Manager";
        }
    };

    public abstract LeaveRequestState nextState();
    public abstract String responsiblePerson();
}
この例では、「ステートマシンの遷移は、enumの抽象メソッド」を使用して実装されています。 より正確には、各列挙定数で_nextState()_を使用して、次の状態への遷移を指定します。 必要に応じて、_previousState()_メソッドを実装することもできます。
以下は、実装を確認するためのテストです。
LeaveRequestState state = LeaveRequestState.Submitted;

state = state.nextState();
assertEquals(LeaveRequestState.Escalated, state);

state = state.nextState();
assertEquals(LeaveRequestState.Approved, state);

state = state.nextState();
assertEquals(LeaveRequestState.Approved, state);
_Submitted_初期状態で休暇申請を開始します。 次に、上記で実装した_nextState()_メソッドを使用して、状態遷移を検証します。
  • since _Approved_が最終状態であり、他の遷移は発生しないことに注意してください*。

*5. Java Enums *を使用してステートマシンを実装する利点

インターフェイスと実装クラスを備えたlink:/java-state-design-pattern [ステートマシンの実装]は、開発と保守を行うためのかなりの量のコードになる可能性があります。
Java列挙型は、最も単純な形式では定数のリストであるため、列挙型を使用して状態を定義できます。 また、列挙には動作を含めることもできるため、メソッドを使用して状態間の遷移実装を提供できます。
*すべてのロジックを単純な列挙型にすると、簡潔でわかりやすいソリューションが可能になります。*

6. 結論

この記事では、ステートマシンと、Enumを使用してJavaでステートマシンを実装する方法について説明しました。 例を挙げてテストしました。
最終的に、列挙型を使用してステートマシンを実装する利点についても説明しました。 インターフェイスと実装ソリューションの代替として、enumは、ステートマシンのより明確でわかりやすい実装を提供します。
いつものように、この記事で言及されているすべてのコードスニペットは、https://github.com/eugenp/tutorials/tree/master/algorithms-miscellaneous-3 [GitHubリポジトリ]にあります。