Stateパターンとは?
Stateパターンは、オブジェクトの「状態」をクラスとしてカプセル化し、状態遷移(状態の変化)に応じてオブジェクトの振る舞いを変えるデザインパターンです。
なぜ使うのか?
通常、状態によって処理を変える場合は if 文や switch 文を多用しがちです。しかし、状態が増えるたびに条件分岐が複雑になり、コードの保守性が低下します。Stateパターンを使うことで、「状態ごとのロジック」を個別のクラスに閉じ込めることができ、コードがスッキリします。
1. 状態のインターフェース (IState)
すべての状態で共通のアクション(お金を入れる、ボタンを押すなど)を定義し、何を目的としているかを明確にします。
C#
/// <summary>
/// 自動販売機の状態を定義するインターフェース
/// </summary>
public interface IState
{
// お金を入れる操作
void InsertMoney();
// ボタンを押す操作
void PushButton();
}
2. 具体的な状態クラス(3つの状態)
各状態における振る舞いをクラスとして記述します。各クラスは「自分がその状態の時にどう振る舞うか」だけに集中しています。
C#
// --- お金が入っていない状態 ---
public class NoMoneyState : IState
{
private readonly VendingMachine _machine;
public NoMoneyState(VendingMachine machine) => _machine = machine;
public void InsertMoney()
{
Console.WriteLine("→ お金が投入されました。");
// 状態を「お金あり」へ移行
_machine.SetState(_machine.HasMoneyState);
}
public void PushButton()
{
Console.WriteLine("× [警告] 先にお金を入れてください。");
}
}
// --- お金が入っている状態 ---
public class HasMoneyState : IState
{
private readonly VendingMachine _machine;
public HasMoneyState(VendingMachine machine) => _machine = machine;
public void InsertMoney()
{
Console.WriteLine("× [警告] 既にお金が入っています。");
}
public void PushButton()
{
Console.WriteLine("→ ボタンが押されました。商品を出力します。");
// 在庫を1つ減らす
_machine.ReduceStock();
// 在庫状況に応じて次の状態を判定
if (_machine.StockCount > 0)
{
_machine.SetState(_machine.NoMoneyState); // まだ在庫があれば待機へ
}
else
{
_machine.SetState(_machine.SoldOutState); // 在庫が切れたら売り切れへ
}
}
}
// --- 売り切れ状態 ---
public class SoldOutState : IState
{
private readonly VendingMachine _machine;
public SoldOutState(VendingMachine machine) => _machine = machine;
public void InsertMoney()
{
Console.WriteLine("× [エラー] 売り切れです。お金は入りません。");
}
public void PushButton()
{
Console.WriteLine("× [エラー] 商品がないため、操作できません。");
}
}
3. コンテキストクラス(自動販売機本体)
このクラスは「現在の状態」を保持し、処理を委譲(丸投げ)する役割です。
C#
/// <summary>
/// 自動販売機本体。各状態のインスタンスと在庫を管理する。
/// </summary>
public class VendingMachine
{
// 各状態のインスタンス(外部から切り替えられるよう公開)
public IState NoMoneyState { get; }
public IState HasMoneyState { get; }
public IState SoldOutState { get; }
// 現在の状態
private IState _currentState;
// 商品在庫数
public int StockCount { get; private set; }
public VendingMachine(int initialStock)
{
// 1. 各状態の初期化
NoMoneyState = new NoMoneyState(this);
HasMoneyState = new HasMoneyState(this);
SoldOutState = new SoldOutState(this);
StockCount = initialStock;
// 2. 初期状態の設定
_currentState = (initialStock > 0) ? NoMoneyState : SoldOutState;
}
// 状態を切り替えるメソッド
public void SetState(IState newState)
{
_currentState = newState;
}
// 在庫を減らす処理
public void ReduceStock()
{
if (StockCount > 0) StockCount--;
Console.WriteLine($" (現在の在庫: {StockCount})");
}
// --- 外部API(利用者からの操作) ---
// 実際の処理は「現在の状態クラス」へ完全に委譲(Delegation)する
public void InsertMoney() => _currentState.InsertMoney();
public void PushButton() => _currentState.PushButton();
}
実行イメージ(Mainメソッド)
C#
class Program
{
static void Main()
{
// 自動販売機を作成(商品在庫数 2で初期化)
var machine = new VendingMachine(2);
// 1回目:購入
machine.InsertMoney();
machine.PushButton();
// 2回目:購入(これで在庫が切れる)
machine.InsertMoney();
machine.PushButton();
// 3回目:売り切れ後に操作してみる
machine.InsertMoney();
machine.PushButton();
}
}
Stateパターンのまとめ
- 条件分岐が消えた:
VendingMachineクラスの中にif (stock == 0)やif (isMoneyInserted)といったフラグ管理が一切ありません。 - 状態の独立性: 「売り切れの時にどう動くか」を修正したい場合は、
SoldOutStateクラスだけを見れば完結します。 - 拡張性: 例えば「メンテナンス中」という状態を増やしたい時は、新しいクラスを作って
SetStateするだけで済み、既存の「お金あり」などのロジックを壊す心配がありません。

最近のコメント