「State」パターンとは?サンプルを踏まえてわかりやすく解説!【Java】

  • LINEで送る
「State」パターンとは?サンプルを踏まえてわかりやすく解説!

重要度:★★★★☆

 本記事ではGoFのデザインパターンのプログラムの振る舞いに関するパターンの一つである「State」パターンを解説します。このパターンを一言で説明するならば、「状態をオブジェクトとし、そのオブジェクトを切り替えることによって状態の変化を表現する方法」と言えるでしょう。「State」パターンをサンプルを踏まえて解説します。

Stateパターンとは

 Stateパターンは、オブジェクトの状態に応じて振る舞いが変わるようにするデザインパターンです。状態ごとにクラスを分けて、状態ごとのロジックを明確に管理します。

 日常生活の例で信号機のケースを考えてみます。信号機は以下の3つのサイクルで変わっていきます。

  1. 赤色:止まれ
  2. 青色:進め
  3. 黄色:注意して停止、または進む(短い時間)
Stateパターンのイメージ図

 オブジェクト指向では信号機をObjectとし、そのフィールドで信号の色等の状態を持っていますが、Stateパターンでは状態自体をObjectとして、そのObjectを切り替えることで状態の変化を表現します。

Stateパターンの構成要素

 Stateパターンは非常にシンプルで、3つの構成要素から実装することができます。

要素役割
Context状態を保持し、操作を状態オブジェクトに委譲するクラス
※信号機のケースでの例
・信号機本体
State(状態インターフェース)状態ごとに共通の振る舞いを定義するインターフェース
ConcreteState(具体的状態)Stateインターフェースを実装し、状態ごとの具体的な動作を定義
※信号機のケースでの例
・赤、黄、緑の状態を表すオブジェクト

Stateパターンの書き方

 実際に先ほどの信号機の例でStateパターンを実装します。

Stateを使わない場合

 まずStateパターンを利用しないで信号機の仕組みを実装するとどのようになるでしょうか。おそらく下記のように現在の信号機の色を、文字列やenumで持つようになるかと思います。

public class TrafficLightWithoutState {
    private String state;

    public TrafficLightWithoutState() {
        this.state = "RED"; // 初期状態
    }

    public void showSignal() {
        switch (state) {
            case "RED":
                System.out.println("赤信号:止まってください。");
                break;
            case "GREEN":
                System.out.println("青信号:進んでください。");
                break;
            case "YELLOW":
                System.out.println("黄信号:注意して停止してください。");
                break;
            default:
                System.out.println("不明な状態です。");
        }
    }

    public void change() {
        switch (state) {
            case "RED":
                state = "GREEN";
                break;
            case "GREEN":
                state = "YELLOW";
                break;
            case "YELLOW":
                state = "RED";
                break;
            default:
                state = "RED";
        }
    }
}

 この実装でも、信号機の仕組みは実装できます。ではなぜこの実装だと問題があるのでしょうか。以下にこの実装方法の問題点をまとめます。

問題点説明
条件分岐が多くなる状態が増えるたびにswitchやif文の条件分岐が増える
例)右折専用の右方向の矢印を追加する場合
拡張性が低い条件分岐が多くなるということは、その分クラス内の拡張性が低下する
単一責任の原則に違反状態遷移と状態ごとの振る舞いが1クラスに集中しているため、そのクラスの責務が曖昧になる。

 Stateを使う場合

 上記の問題点を解決できるのがStateパターンです。では実際にStateパターンを利用して信号機の仕組みを実装していきます。

 まずはState(状態インターフェース)を定義します。

public interface TrafficLightState {
    void showSignal();
    void next(TrafficLight trafficLight);
}

 それぞれのメソッドについては下記になります。

メソッド役割
void showSignal()現在のStateを表示する
void next(TrafficLight trafficLight)次のStateに移行する

 次にこのStateを実装する形で赤、黄、青の状態クラスを実装します。

public class RedState implements TrafficLightState {
    @Override
    public void showSignal() {
        System.out.println("Red light is on.");
    }

    @Override
    public void next(TrafficLight trafficLight) {
        trafficLight.setState(new GreenState());
    }
}

public class YellowState implements TrafficLightState {
    @Override
    public void showSignal() {
        System.out.println("Yellow light is on.");
    }

    @Override
    public void next(TrafficLight trafficLight) {
        trafficLight.setState(new RedState());
    }
}

public class GreenState implements TrafficLightState {
    @Override
    public void showSignal() {
        System.out.println("Green light is on.");
    }

    @Override
    public void next(TrafficLight trafficLight) {
        trafficLight.setState(new YellowState());
    }
}

 本記事ではshowSignal()は標準出力するだけのメソッドです。本来はここで状態ごとの処理を実装します。nextではcontextクラスであるTrafficLightを引数で受け取って、そのオブジェクトの状態を新しいStateに上書きします。

 次にnext()メソッドで出てきたcontextクラスであるTrafficLightクラスを実装します。

public class TrafficLight {
    private TrafficLightState currentState;

    public TrafficLight() {
        currentState = new RedState();
    }

    public void setState(TrafficLightState state) {
        currentState = state;
    }

    public void showSignal() {
        currentState.showSignal();
    }

    public void change() {
        currentState.next(this);
    }
}

 TrafficLightクラスは状態を持つために、TrafficLightState型のフィールドを持っています。このフィールドに入るインスタンスを切り替えることで信号の状態を表現します。またcurrentStateの初期値は引数なしのコンストラクタ内でRedStateで初期化しています。

 showSignal()では現在のStateのshowSignal()メソッドを呼び出し、現在の状態を出力します。またchange()メソッドでは、現在のStateのnext()メソッドを呼び出して、状態の上書きを行います。

 最後にこのTrafficLightクラスを使ってみます。何度かchange()メソッドを呼ぶためにfor文で6回回しています。

public class StateSample {
    public static void main(String[] args) {
        TrafficLight signal = new TrafficLight();

        for (int i = 0; i < 6; i++) {
            signal.showSignal();
            signal.change(); // 状態を次に変更
        }
    }
}
>> 実行結果
Red light is on.
Green light is on.
Yellow light is on.
Red light is on.
Green light is on.
Yellow light is on.

 これでStateを利用して、信号機の仕組みを実装することができました。if文やSwitch文を利用していないため非常に拡張しやすい構造となっています。

メリットとデメリット

メリット

メリット説明
状態ごとの処理を分離できる各状態の振る舞いが個別クラスに分かれ、コードが見やすい
状態遷移が明確になるnext() によって、次の状態への切り替えが直感的に書ける
条件分岐を減らせるif/switch文ではなく、クラスで振る舞いを切り替える

デメリット

デメリット説明
状態が少ないと逆に複雑化信号のように3つだけなら、簡易ifでも十分な場合もある
クラスファイルが多くなる各状態ごとに1クラス必要なので、管理がやや面倒になることも

 Stateパターンには上記のメリットとデメリットがあります。これらのメリットとデメリットから、Stateパターンが有効なシチュエーションには下記のような場合があります。

  • 申請・承認フロー管理
    • 例:休暇申請、経費精算など
    • 状態ごとに許可される操作が異なるため、状態別の処理を明確に分離できる
  • UI・操作状態の管理
    • 例:接続状態、フォームステップなど
    • 状態に応じたUI制御が必要な場面で、振る舞いを状態クラスに切り分けられる
  • 機器やデバイスのモード制御
    • プリンター、洗濯機、冷蔵庫など
    • モードに応じて動作が変わるため、状態ごとのロジックを整理しやすい

まとめ

 Stateパターンは、状態ごとの振る舞いを個別のクラスとして分離することで、複雑な条件分岐を避け、保守性・拡張性を高めることができます。特に、ワークフローの申請ステータス管理、UIの状態遷移、家電の動作モード管理など、「状態に応じて処理が変化する」場面で効果を発揮します。

 状態ごとの責務を明確にし、オブジェクトの状態遷移を自然な形でコードに表現できるため、状態の増加や仕様変更にも柔軟に対応できます。

 本記事で利用したサンプルケースのクラス図は下記になります。

 最後に「State」パターンとは「状態をオブジェクトととし、そのオブジェクトを切り替えることによって状態の変化を表現する方法」です。うまく使っていきましょう。

 本記事で利用したコードは下記のgit上で公開しております。

https://github.com/tamotech1028/state


デザインパターン講座 目次

第1章 導入

第2章 オブジェクトの「生成」に関するパターン

第3章 プログラムの「構造」に関するパターン

第4章 オブジェクトの「振る舞い」に関するパターン

最新の投稿

SNSでもご購読できます。

コメント

コメントを残す