[Design Pattern] State Pattern

State Pattern

상태에 따라 기능이 다르게 동작해야 할 때 사용할 수 있는 패턴

예시 상황

자판기 프로그램 구현

public class VendingMachine {
    
    // NOCOIN: 동전이 없는 상태
    // SELECTABLE: 동전이 투입되어 제품을 선택할 수 있는 상태    
    public static enum State { NOCOIN, SELECTABLE }

    private State state = State.NOCOIN;
    
    // 동전이 투입되었을 때 동작
    public void insertCoin(int coin) {
        switch (state) {
            case NOCOIN:
                increaseCoin(coin);
                state = State.SELECTABLE;
                break;
            case SELECTABLE:
                increaeCoin(coin);
        }
    }

    // 제품을 선택했을 때 동작
    public void select(int productId) {
        switch (state) {
            case NOCOIN:
                // 아무 것도 하지 않음
                break;
            case SELECTABLE:
                provideProduct(productId);
                decreaseCoin();
                if (hasNoCoin()) {
                    state = State.NOCOIN;
                }
        }
    }
}

추가 요구 사항

public class VendingMachine {
    
    // NOCOIN: 동전이 없는 상태
    // SELECTABLE: 동전이 투입되어 제품을 선택할 수 있는 상태    
    // SOLDOUT: 제품이 없는 경우
    public static enum State { NOCOIN, SELECTABLE, SOLDOUT }

    private State state = State.NOCOIN;
    
    // 동전이 투입되었을 때 동작
    public void insertCoin(int coin) {
        switch (state) {
            case NOCOIN:
                increaseCoin(coin);
                state = State.SELECTABLE;
                break;
            case SELECTABLE:
                increaeCoin(coin);
                break;
            // SOLDOUT이 추가됨
            case SOLDOUT:
                returnCoin();
                break;
        }
    }

    // 제품을 선택했을 때 동작
    public void select(int productId) {
        switch (state) {
            case NOCOIN:
                // 아무 것도 하지 않음
                break;
            case SELECTABLE:
                provideProduct(productId);
                decreaseCoin();
                if (hasNoCoin()) {
                    state = State.NOCOIN;
                }
                break;
            // SOLDOUT이 추가됨
            case SOLDOUT:
                // 아무 것도 하지 않음
                break;
        }
    }
}

상태 패턴 적용

동전 증가 처리와 제품 선택 처리를 할 수 있는 인터페이스를 제공하고 콘크리트 클래스에서 기능을 구현한다.

상태 패턴을 적용한 VendingMachine 구현

public class VendingMachine {

    private State state;

    public VendingMachine() {
        state = new NoCoinState();
    }

    public void insertCoin(int coin) {
        state.increaseCoin(coin, this);
    }

    public void select(int productId) {
        state.select(productId, this);
    }

    public void changeState(State newState) {
        this.state = newState;
    }
}

동전 증가 처리와 제품 선택 처리를 할 수 있는 두 개의 메서드를 정의하고 있는 State 인터페이스

interface State {

    public void increaseCoin(int coin, VendingMachine vm);

    public void select(int productId, VendingMachine vm);
}

동작 없음 상태일 때의 동작을 구현한 NoCoinState 클래스

public class NoCoinState implements State {

    @Override
    public void increaseCoin(int coin, VendingMachine vm) {
        vm.increaseCoin(coin);
        vm.changeState(new SelectableState());
    }

    @Override
    public void select(int productId, VendingMachine vm) {
        SoundUtil.beep();
    }
}

음료 가능 상태의 동작을 구현한 SelectableState 클래스

public class SelectableState implements State {

    @Override
    public void increaseCoin(int coin, VendingMachine vm) {
        vm.increaseCoin(coin);
    }

    @Override
    public void select(int productId, VendingMachine vm) {
        vm.provideProduct(productId);
        vm.decreaseCoin();

        if (vm.hasNoCoin()) {
            vm.changeState(new NoCoinState());
        }
    }
}

Reference