状態パターンの実装方法

状態パターン(State Pattern)は、オブジェクトの内部状態に応じて振る舞いを変更するデザインパターンです。このパターンでは、状態ごとに処理を分離して管理することで、複雑な条件分岐を回避し、コードの可読性と拡張性を高めます。

適用ケース

状態パターンは、オブジェクトの状態遷移が複雑になり、if-elseやswitch文の条件分岐が肥大化しがちな場合に特に有効です。状態ごとに専用のクラスを定義することで、状態変化に伴う振る舞いの変更をローカルに閉じ込め、保守性を向上させます。

実装例:ガムボールマシン

以下は、ガムボールマシンの状態遷移を状態パターンで実装した例です。マシンは「投幣待ち」「投幣済み」「販売中」「在庫切れ」の4つの状態を持ち、それぞれの状態で異なる振る舞いをします。

1. 状態インターフェース


package com.example.state;

public interface MachineState {
    void insertCoin();
    void returnCoin();
    void turnHandle();
    void dispense();
}

2. 投幣待ち状態


package com.example.state;

public class WaitingForCoinState implements MachineState {
    private GumballMachine machine;

    public WaitingForCoinState(GumballMachine machine) {
        this.machine = machine;
    }

    @Override
    public void insertCoin() {
        System.out.println("コインを確認しました");
        machine.changeState(machine.getHasCoinState());
    }

    @Override
    public void returnCoin() {
        System.out.println("コインを返却できません。まだ投入されていません");
    }

    @Override
    public void turnHandle() {
        System.out.println("ハンドルを回しましたが、コインがありません");
    }

    @Override
    public void dispense() {
        System.out.println("まずコインを支払ってください");
    }

    @Override
    public String toString() {
        return "コイン待ち";
    }
}

3. 投幣済み状態


package com.example.state;

public class HasCoinState implements MachineState {
    private GumballMachine machine;

    public HasCoinState(GumballMachine machine) {
        this.machine = machine;
    }

    @Override
    public void insertCoin() {
        System.out.println("すでにコインを挿入しています");
    }

    @Override
    public void returnCoin() {
        System.out.println("コインを返却しました");
        machine.changeState(machine.getNoCoinState());
    }

    @Override
    public void turnHandle() {
        System.out.println("ハンドルを回しました");
        machine.changeState(machine.getSoldState());
    }

    @Override
    public void dispense() {
        System.out.println("ガムボールを排出できません");
    }

    @Override
    public String toString() {
        return "ハンドル待ち";
    }
}

4. 在庫切れ状態


package com.example.state;

public class SoldOutState implements MachineState {
    private GumballMachine machine;

    public SoldOutState(GumballMachine machine) {
        this.machine = machine;
    }

    @Override
    public void insertCoin() {
        System.out.println("在庫切れです。コインを受け付けられません");
    }

    @Override
    public void returnCoin() {
        System.out.println("コインを返却できません。まだ投入されていません");
    }

    @Override
    public void turnHandle() {
        System.out.println("ハンドルを回しましたが、ガムボールがありません");
    }

    @Override
    public void dispense() {
        System.out.println("排出するガムボールがありません");
    }

    @Override
    public String toString() {
        return "在庫切れ";
    }
}

5. 販売中状態


package com.example.state;

public class SoldState implements MachineState {
    private GumballMachine machine;

    public SoldState(GumballMachine machine) {
        this.machine = machine;
    }

    @Override
    public void insertCoin() {
        System.out.println("現在ガムボールを排出中です");
    }

    @Override
    public void returnCoin() {
        System.out.println("すでにハンドルを回しました。返金できません");
    }

    @Override
    public void turnHandle() {
        System.out.println("もう一度回してもガムボールは出ません");
    }

    @Override
    public void dispense() {
        machine.releaseBall();
        if (machine.getCount() > 0) {
            machine.changeState(machine.getNoCoinState());
        } else {
            System.out.println("在庫がなくなりました!");
            machine.changeState(machine.getSoldOutState());
        }
    }

    @Override
    public String toString() {
        return "ガムボール排出中";
    }
}

6. 状態を管理するマシンクラス


package com.example.state;

public class GumballMachine {
    private MachineState noCoinState;
    private MachineState hasCoinState;
    private MachineState soldState;
    private MachineState soldOutState;

    private MachineState currentState;
    private int ballCount;

    public GumballMachine(int count) {
        this.noCoinState = new WaitingForCoinState(this);
        this.hasCoinState = new HasCoinState(this);
        this.soldState = new SoldState(this);
        this.soldOutState = new SoldOutState(this);

        this.ballCount = count;
        this.currentState = (count > 0) ? noCoinState : soldOutState;
    }

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

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

    public void turnHandle() {
        currentState.turnHandle();
        currentState.dispense();
    }

    void changeState(MachineState state) {
        this.currentState = state;
    }

    void releaseBall() {
        System.out.println("ガムボールが排出されました");
        if (ballCount > 0) {
            ballCount--;
        }
    }

    public int getCount() {
        return ballCount;
    }

    public MachineState getNoCoinState() {
        return noCoinState;
    }

    public MachineState getHasCoinState() {
        return hasCoinState;
    }

    public MachineState getSoldState() {
        return soldState;
    }

    public MachineState getSoldOutState() {
        return soldOutState;
    }
}

7. 動作確認用テストクラス


package com.example.state;

public class GumballMachineTest {
    public static void main(String[] args) {
        GumballMachine machine = new GumballMachine(3);

        System.out.println(machine);

        machine.insertCoin();
        machine.turnHandle();

        System.out.println(machine);

        machine.insertCoin();
        machine.turnHandle();

        System.out.println(machine);

        machine.insertCoin();
        machine.turnHandle();

        System.out.println(machine);
    }
}

タグ: Java デザインパターン 状態パターン オブジェクト指向 OOP

7月8日 17:56 投稿