본문 바로가기
컴퓨터과학/디자인패턴

[디자인패턴] 상태 패턴 (State Pattern)

by 윤호 2021. 12. 11.

목적

객체의 내부 상태가 바뀔 때 객체의 동작을 변경할 수 있도록 함.

객체는 자신의 클래스를 바꾸는 것 처럼 보임.

요소

문제 : 상태가 여러개 있고, 이를 if 문으로 통제함

해결 : 상태를 한 곳에서 관리

결과 : 변경 최소화

정의

A상태의 handle과 B상태의 handle의 동작이 다르다. -> 상태가 다르면 객체의 동작이 다름

 

뽑기 기계 구현

뽑기 기계는 4 가지의 상태(매진, 동전없음, 동전있음, 판매)가 있고,

다음의 4 가지 메소드(동전 투입, 반환, 손잡이 돌림, 알맹이 내보냄)가 있다.

 

상태 패턴을 사용하지 않고 이를 구현하려면 메소드마다 state에 대한 조건문을 구현해야 한다. (4*4 = 16가지를 구현)

만약 state가 추가된다면 조건문을 모든 메소드에 추가해야 된다 -> 문제점

 

상태 패턴을 이용한 뽑기 기계

상태마다 클래스를 구현하고 뽑기기계는 이 상태 객체를 갈아끼우면서 동작한다.

각 클래스의 메소드는 상태마다 알맞게 구현한다.

새로운 상태가 추가되면 그 상태와 연관된 클래스들만 수정하면 된다.

뽑기기계 클래스

public class GumballMachine {
    State soldOutState;
    State noQuarterState;
    State hasQuarterState;
    State soldState;
    State state;
    int count = 0;

    public GumballMachine(int numberGumballs) {
        soldOutState = new SoldOutState(this);
        noQuarterState = new NoQuarterState(this);
        hasQuarterState = new HasQuarterState(this);
        soldState = new SoldState(this);
        state = soldOutState;
        this.count = numberGumballs;
        if (numberGumballs > 0) {
            state = noQuarterState;
        }
    }

    public void insertQuarter() {
        state.insertQuarter();
    }

    public void ejectQuarter() {
        state.ejectQuarter();
    }

    public void turnCrank() {
        state.turnCrank();
        state.dispense();
    }
    
    public void setState(State state) {
      this.state = state;
    }
    
    ... // 상태 객체를 반환하는 메소드들 getHasQuarterState(), getSoldState(), ...

}

 

상태 클래스들

메소드가 뽑기 기계의 상태를 바꾸도록 구현 - insertQuater()를 실행하면 HasQuater 상태로 기계를 바꿈 -> 기계의 메소드 내용이 바뀜

// 동전 없음 상태
public class NoQuarterState implements State {
    GumballMachine gumballMachine;

    public NoQuarterState(GumballMachine gbMachine) {
        gumballMachine = gbMachine;
    }

    public void insertQuarter() {
        System.out.println("동전을 넣으셨습니다");
        // 동전을 넣은 상태로 변경
        gumballMachine.setState(gumballMachine.getHasQuarterState());
    }
    
    public void ejectQuarter() {
        System.out.println("동전을 넣어주세요");
    }
    
    public void turnCrank(){
        System.out.println("동전을 넣어주세요");
    }

    public void dispense(){
        System.out.println("동전을 넣어주세요");
    }
}

 

// 동전 있음 상태
public class HasQuarterState implements State {
    GumballMachine gumballMachine;

    public HasQuarterState(GumballMachine gbMachine) {
        gumballMachine = gbMachine;
    }

    public void insertQuarter() {
        System.out.println("동전은 한 개만 넣어주세요");
    }

    public void ejectQuarter() {
        System.out.println("동전이 반환됩니다");
        gumballMachine.setState(gumballMachine.getNoQuarterState());
    }

    public void turnCrank() {
        System.out.println("손잡이를 돌리셨습니다");
        gumballMachine.setState(gumballMachine.getSoldState());
    }


    public void dispense() {
        System.out.println("알맹이가 나갈 수 없습니다");
    }
}

 

// 판매 상태
public class SoldState implements State {
    GumballMachine gumballMachine;

    public SoldState(GumballMachine gbMachine) {
        gumballMachine = gbMachine;
    }

    public void insertQuarter() {
        System.out.println("잠깐만 기다려 주세요. 알맹이가 나가고 있습니다");
    }

    public void ejectQuarter() {
        System.out.println("이미 알맹이를 뽑으셨습니다");
    }

    public void turnCrank() {
        System.out.println("손잡이는 한 번만 돌려주세요");
    }

    public void dispense() {
        gumballMachine.releaseBall();
        if (gumballMachine.getCount() > 0) {
            gumballMachine.setState(gumballMachine.getNoQuarterState());
        } else {
            System.out.println("Oops, out of gumballs!");
            gumballMachine.setState(gumballMachine.getSoldOutState());
        }
    }
}

 

뽑기 기계를 사용하는 메인 메소드

    public static void main(String[] args) {
        GumballMachine gumballMachine = new GumballMachine(5);
        System.out.println(gumballMachine);
        
        gumballMachine.insertQuarter();
        gumballMachine.turnCrank();
    }

 

댓글