はじめに
本記事は、GoFデザインパターン解説シリーズの1つです。
今回は、振る舞いに関するパターンの中のState(ステート)パターンを解説します。
「オブジェクトの内部状態によって振る舞いが変わる」処理を、スッキリ整理するためのパターンです。
解決したい課題
「オブジェクトの状態によって、同じ操作でも振る舞いが変わる」処理を、素朴に実装しようとすると、巨大なif文やswitch文で状態ごとの分岐を書くことになりがちです。
例えば、自動販売機を考えます。
「お金投入前」「お金投入済み」「売り切れ」という状態によって、「商品ボタンを押す」という同じ操作の結果が変わります。
これを1つのクラスの中でswitch(state)のように分岐すると、状態が増えるたびにすべてのメソッドのswitch文に新しい分岐を追加しなければならず、修正漏れのリスクも高まり、コードも急速に肥大化していきます。
Stateパターンは、「状態」自体を1つのオブジェクトとして切り出し、状態ごとに振る舞いを持つクラスに分離することで、この問題を解決します。
クラス構成
- Context(コンテキスト): 現在の状態(State)への参照を持ち、状態に応じた処理を状態オブジェクトに委譲するクラス
- State(ステート): 状態ごとの振る舞いを定義する、共通インターフェース
- ConcreteState(具体的なステート): 特定の状態における、具体的な振る舞いを実装するクラス
Javaでの実装例
自動販売機を例に実装してみます。
State:共通インターフェース
public interface VendingMachineState {
void insertCoin(VendingMachine machine);
void selectProduct(VendingMachine machine);
}
Context:自動販売機本体
public class VendingMachine {
// 現在の状態への参照。状態が切り替わるとこのフィールドが差し替わる
private VendingMachineState state;
public VendingMachine() {
this.state = new NoCoinState(); // 初期状態: お金投入前
}
public void setState(VendingMachineState state) {
this.state = state;
}
// Context自身は「何をすべきか」を判断せず、現在の状態オブジェクトに処理を委譲する
public void insertCoin() {
state.insertCoin(this);
}
public void selectProduct() {
state.selectProduct(this);
}
}
ConcreteState:状態ごとの具体的な振る舞い
public class NoCoinState implements VendingMachineState {
@Override
public void insertCoin(VendingMachine machine) {
System.out.println("[お金投入前] コインを受け付けました");
machine.setState(new HasCoinState()); // 「お金投入済み」状態へ遷移
}
@Override
public void selectProduct(VendingMachine machine) {
System.out.println("[お金投入前] お金が投入されていません");
}
}
public class HasCoinState implements VendingMachineState {
@Override
public void insertCoin(VendingMachine machine) {
System.out.println("[お金投入済み] すでにコインが投入されています");
}
@Override
public void selectProduct(VendingMachine machine) {
System.out.println("[お金投入済み] 商品を排出しました");
machine.setState(new SoldOutState()); // 在庫がなくなった想定で「売り切れ」状態へ遷移
}
}
public class SoldOutState implements VendingMachineState {
@Override
public void insertCoin(VendingMachine machine) {
System.out.println("[売り切れ] 只今売り切れ中です。コインを返却します");
}
@Override
public void selectProduct(VendingMachine machine) {
System.out.println("[売り切れ] 商品がありません");
}
}
利用側のコード
public class Main {
public static void main(String[] args) {
VendingMachine machine = new VendingMachine();
machine.selectProduct(); // お金投入前に選択 → 拒否される
machine.insertCoin(); // コイン投入 → 状態が変わる
machine.selectProduct(); // 商品選択 → 排出され、売り切れ状態になる
machine.insertCoin(); // 売り切れ状態でコイン投入 → 返却される
}
}
実行結果:
[お金投入前] お金が投入されていません
[お金投入前] コインを受け付けました
[お金投入済み] 商品を排出しました
[売り切れ] 只今売り切れ中です。コインを返却します
VendingMachine(Context)自身は、if文やswitch文による状態判定を一切行っていません。
「現在の状態オブジェクトに処理を委譲する」だけです。状態ごとの振る舞いは、それぞれ独立したNoCoinState・HasCoinState・SoldOutStateクラスに分かれており、新しい状態(例えば「メンテナンス中」)を追加したい場合も、新しいクラスを1つ追加するだけで対応でき、既存の状態クラスには一切手を入れる必要がありません。
Strategyパターンとの違い
Stateパターンは、クラス構造だけを見ると次回解説するStrategyパターンと非常によく似ています(どちらも「振る舞いをオブジェクトとして切り出し、委譲する」構造です)。
違いは意図と状態遷移の有無にあります。
Strategyは「アルゴリズムを外部から選んで注入する」ことが目的で、通常は実行中に自動的に切り替わることを想定しません。
一方Stateは、「オブジェクト自身の内部状態に応じて振る舞いが変わり、かつ状態自身が次の状態への遷移を引き起こす」ことを想定しています。
上記の例で、各State実装クラスがmachine.setState(...)を呼んで自ら次の状態への遷移を行っている点が、この違いを表しています。
使用場面
- 自動販売機、信号機、注文のステータス管理(未処理→処理中→発送済み→完了)など、明確な状態遷移を持つ処理
- 同じ操作(メソッド)でも、オブジェクトの状態によって振る舞いが大きく異なる場合
- 巨大な
if/switchによる状態分岐が、あちこちのメソッドに散らばってしまっている場合の整理 - ゲームにおけるキャラクターの状態(通常・攻撃中・スタン中など)の管理
まとめ
| 項目 | 内容 |
|---|---|
| 解決する課題 | オブジェクトの状態によって振る舞いが変わる処理が、巨大なif/switch文になってしまう |
| 実現方法 | 状態ごとの振る舞いを、それぞれ独立したStateクラスに切り出す |
| メリット | 新しい状態の追加が、既存コードを変更せずに新クラスの追加だけで済む |
| Strategyとの違い | Stateは状態遷移を伴う自己駆動、Strategyは外部からアルゴリズムを選んで注入する |
おすすめ書籍
GoFの23パターンをより深く学びたい方には、以下の書籍がおすすめです。
Java言語のサンプルコードとUMLを使い、初心者にもわかりやすく解説されています。
Java言語で学ぶデザインパターン入門 第3版 [ 結城 浩 ]
※本リンクはアフィリエイトリンクを含みます。


