본문 바로가기
스터디/헤드퍼스트 디자인패턴

[헤드퍼스트 디자인패턴] 09. 상태 패턴(State Pattern)

by 디토20 2022. 8. 7.
반응형

 

 

 

[헤드퍼스트 디자인패턴] 09. 상태 패턴(State Pattern)

 

1. 상태 패턴이란?

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

 

 

2. 상태 기계 기초 지식 알아보기

뽑기 기계를 만들때, 상태를 이용해 기계를 구현하는 방법을 간단하게 살펴보자.

 

2.1 우선 상태들을 모아본다.

총 4개의 상태가 존재한다.

(No Quarter : 동전 없음, Has Quarter : 동전 있음, Gumball Sold : 알맹이 판매, Out of Gumballs : 알맹이 매진)

 

 

2.2 현재 상태를 저장하는 인스턴스 변수를 만들고 각 상태의 값을 정의한다.

final static int SOLD_OUT = 0;    
final static int NO_QUARTER = 1;    
final static int HAS_QUARTER = 2;    
final static int SOLD = 3;

 

 

2.3 이 시스템에서 일어날 수 있는 모든 행동을 모아 본다.

  • 동전 투입
  • 동전 반환
  • 손잡이 돌림
  • 알맹이 내보냄

 

2.4 뽑기 기계 코드를 구현해본다.

public class GumballMachine {
    final static int SOLD_OUT = 0;
    final static int NO_QUARTER = 1;
    final static int HAS_QUARTER = 2;
    final static int SOLD = 3;

    int state = SOLD_OUT;
    int count = 0;

    public GumballMachine(int count) {
        this.count = count;
        if (count > 0) {
            state = NO_QUARTER;
        }
    }

    public void insertQuarter() {
        if (state == HAS_QUARTER) {
            System.out.println("You can't insert another quarter");
        } else if (state == NO_QUARTER) {
            state = HAS_QUARTER;
            System.out.println("You inserted a quarter");
        } else if (state == SOLD_OUT) {
            System.out.println("You can't insert a quarter, the machine is sold out");
        } else if (state == SOLD) {
            System.out.println("Please wait, we're already giving you a gumball");
        }
    }

    public void ejectQuarter() {
        if (state == HAS_QUARTER) {
            System.out.println("Quarter returned");
            state = NO_QUARTER;
        } else if (state == NO_QUARTER) {
            System.out.println("You haven't inserted a quarter");
        } else if (state == SOLD) {
            System.out.println("Sorry, you already turned the crank");
        } else if (state == SOLD_OUT) {
            System.out.println("You can't eject, you haven't inserted a quarter yet");
        }
    }

    public void turnCrank() {
        if (state == SOLD) {
            System.out.println("Turning twice doesn't get you another gumball!");
        } else if (state == NO_QUARTER) {
            System.out.println("You turned but there's no quarter");
        } else if (state == SOLD_OUT) {
            System.out.println("You turned, but there are no gumballs");
        } else if (state == HAS_QUARTER) {
            System.out.println("You turned...");
            state = SOLD;
            dispense();
        }
    }

    private void dispense() {
        if (state == SOLD) {
            System.out.println("A gumball comes rolling out the slot");
            count = count - 1;
            if (count == 0) {
                System.out.println("Oops, out of gumballs!");
                state = SOLD_OUT;
            } else {
                state = NO_QUARTER;
            }
        } else if (state == NO_QUARTER) {
            System.out.println("You need to pay first");
        } else if (state == SOLD_OUT) {
            System.out.println("No gumball dispensed");
        } else if (state == HAS_QUARTER) {
            System.out.println("No gumball dispensed");
        }
    }

    public void refill(int numGumBalls) {
        this.count = numGumBalls;
        state = NO_QUARTER;
    }

    public String toString() {
	// 구현
    }
}

 

-> 위와 같이 구현하면, 새로운 추가 요청 사항이 생겼을 때 모든 메서드에 조건문을 전부 추가해줘야한다.

-> 확장의 어려움

 

 

 

3. 새로운 디자인 구성

  • 뽑기 기계와 관련된 모든 행동에 관한 메소드가 들어있는 State 인터페이스를 정의
  • 기계의 모든 상태를 대상으로 상태 클래스를 구현
  • 조건문 코드를 전부 없애고 상태 클래스에 모든 작업을 위임

 

 

4. State 클래스 구현하기

public class NoQuarterState implements State {
  GumballMachine gumballMachine;
  public NoQuarterState(GumballMachine gumballMachine) {
    this.gumballMachine = gumballMachine;
  }

    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("동전을 넣어주세요");
    }
}

-> 상황에 따라 현재 상태가 다른 상태로 변경될 수 있다.

 

 

5. 뽑기 기계 전체 코드

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);

        this.count = numberGumballs;
         if (numberGumballs > 0) {
            state = noQuarterState;
        } else {
            state = soldOutState;
        }
    }

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

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

    public void turnCrank() {
        state.turnCrank();
        state.dispense();
    }

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

  // 기타 메소드..
}

 

 

6. 구현을 구조적으로 바꿈으로써 얻은 결과

  • 각 상태의 행동을 별개의 클래스로 국지화했다.
  • 관리하기 힘든 골칫덩어리 if문 제거
  • 각 상태를 변경에는 닫혀있게 했고 확장에는 열려있게 수정함(OCP)
  • 더 이해하기 좋은 코드 베이스와 클래스 구조

 

728x90
반응형

댓글