본문 바로가기

디자인 패턴

디자인 패턴 - 상태 패턴(State Pattern)

1. 정의

  • 내부 상태가 바뀜에 따라 객체의 행동이 바뀔 수 있게 한다. 마치 객체의 클래스가 바뀌는 것 같은 결과를 얻을 수 있다.

2. 예시

  • 알맹이 뽑기 기계를 만든다.

3. 그림

4. 클래스 다이어그램

5. 코드

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

        machine.insert();
        machine.eject();

        for(int i=0; i<3; i++) {
            System.out.println("------");
            machine.insert();
            machine.turn();
        }

        System.out.println("------");
        machine.insert();
    }
}
/* 출력
동전을 넣었습니다.
동전을 반환합니다.
------
동전을 넣었습니다.
손잡이를 돌렸습니다
알맹이를 내보냈습니다.
------
동전을 넣었습니다.
손잡이를 돌렸습니다
알맹이를 내보냈습니다.
------
동전을 넣었습니다.
손잡이를 돌렸습니다
알맹이를 내보냈습니다.
알맹이가 매진되었습니다.
------
알맹이가 매진되어 동전을 넣어도 뽑을 수 없습니다.
*/
public interface State {
    void insert();
    void eject();
    void turn();
    void dispense();
}
public class NoState implements State{
    GumballMachine machine;

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

    @Override
    public void insert() {
        System.out.println("동전을 넣었습니다.");
        machine.setState(machine.has);
    }

    @Override
    public void eject() {
        System.out.println("동전이 없어 동전을 뺄 수 없습니다.");
    }

    @Override
    public void turn() {
        System.out.println("동전이 없어 기계를 돌릴 수 없습니다.");
    }

    @Override
    public void dispense() {
        System.out.println("동전이 없어 알맹이를 뽑을 수 없습니다.");
    }
}
public class HasState implements State{
    GumballMachine machine;

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

    @Override
    public void insert() {
        System.out.println("이미 동전이 기계에 있어 넣을 수 없습니다.");
    }

    @Override
    public void eject() {
        System.out.println("동전을 반환합니다.");
        machine.setState(machine.no);
    }

    @Override
    public void turn() {
        System.out.println("손잡이를 돌렸습니다");
        machine.setState(machine.sold);
    }

    @Override
    public void dispense() {
        System.out.println("알맹이를 내보낼 수 없습니다. 손잡이를 돌려주세요.");
    }
}
public class SoldState implements State{
    GumballMachine machine;

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

    @Override
    public void insert() {
        System.out.println("알맹이를 내보내고 있어 동전을 집어넣을 수 없습니다.");
    }

    @Override
    public void eject() {
        System.out.println("알맹이가 뽑히고 있어 동전을 반환할 수 없습니다.");
    }

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

    @Override
    public void dispense() {
        machine.releaseBall();
        if(machine.count > 0){
            machine.setState(machine.no);
        }else{
            System.out.println("알맹이가 매진되었습니다.");
            machine.setState(machine.soldOut);
        }
    }
}
public class SoldOutState implements State{
    GumballMachine machine;

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

    @Override
    public void insert() {
        System.out.println("알맹이가 매진되어 동전을 넣어도 뽑을 수 없습니다.");
    }

    @Override
    public void eject() {
        System.out.println("알맹이가 매진되었고, 삽입된 동전이 없습니다.");
    }

    @Override
    public void turn() {
        System.out.println("알맹이가 매진되어 돌릴 수 없습니다.");
    }

    @Override
    public void dispense() {
        System.out.println("알맹이가 매진되어 뽑을 수 없습니다.");
    }
}
public class GumballMachine {
    State soldOut; // 알맹이 매진
    State no; // 동전 없음
    State has; // 동전 있음
    State sold; // 알맹이 뽑기

    State state;
    int count = 0;

    public GumballMachine(int count) {
        soldOut = new SoldOutState(this);
        no = new NoState(this);
        has = new HasState(this);
        sold = new SoldState(this);

        this.count = count;
        if(count > 0){
            state = no;
        }else{
            state = soldOut;
        }
    }

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

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

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

    // 돌리기와 알맹이 뽑기는 동시에 진행됨
    public void turn(){
        state.turn();
        state.dispense();
    }

    public void releaseBall(){
        System.out.println("알맹이를 내보냈습니다.");
        if(count>0) count--;
    }

}

https://github.com/kang-seongbeom/design_pattern

💡 Github에서 디자인 패턴 코드를 볼 수 있습니다.

6. 설명

  • GumBallMachine은 State 참조변수에게 처리를 위임한다.
  • State 참조변수에 어떤 인스턴스가 저장되었느냐에 따라 행동이 달라진다.
  • 즉, 내부 상태가 바뀜에 따라 행동이 달라진다.