Java/Design Pattern

[헤드퍼스트 디자인 패턴] 10. 상태 패턴

noahkim_ 2024. 12. 18. 22:07

에릭 프리먼 님의 "헤드퍼스트 디자인 패턴" 책을 정리한 포스팅 입니다


1. 상태 기계

  • 상태에 따라 시스템의 행동이 달라지고, 상태 전이는 명확하게 정의된 규칙에 따라 일어남
구성 요소 설명
상태 모음
시스템이 가질 수 있는 여러 상태들을 정의함 (Idle, Running, Paused, Finished 등)
상태 표현 변수
현재 상태를 나타내는 인스턴스 변수 (State currentState;) 등
행동 모음
상태에 따라 달라지는 행동들을 정의함 (start(), pause(), reset() 등)

 

예제: GumballMachine

  • 만약 상태가 추가될 경우, 상태에 의존하는 모든 코드들에 분기 코드가 추가되어야 함

 

예제) GumballMachine

더보기
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, 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("동전은 한 개만 넣어 주세요");
        } else if (state == NO_QUARTER) {
            state = HAS_QUARTER;
            System.out.println("동전을 넣으셨습니다.");
        } else if (state == SOLD_OUT) {
            System.out.println("매진 되었습니다.");
        } else if (state == SOLD) {
            System.out.println("알맹이를 내보내고 있습니다.");
        } 
    }
    
    public void ejectQuarter() {
    	if (state == HAS_QUARTER) {
            System.out.println("동전이 반환됩니다");
            state = NO_QUARTER;            
        } else if (state == NO_QUARTER) {
            System.out.println("동전을 넣어주세요.");
        } else if (state == SOLD_OUT) {
            System.out.println("동전을 넣지 않았습니다.");
        } else if (state == SOLD) {
            System.out.println("이미 알맹이를 뽑으셨습니다.");
        } 
    }
    
    public void turnCrank() {
    	if (state == HAS_QUARTER) {
            System.out.println("손잡이를 돌리셨습니다.");
            state = SOLD;
            dispense();
        } else if (state == NO_QUARTER) {
            System.out.println("동전을 넣어주세요.");
        } else if (state == SOLD_OUT) {
            System.out.println("매진되었습니다.");
        } else if (state == SOLD) {
            System.out.println("손잡이는 한번만 돌려주세요.");
        } 
    }      
    
    public void dispense() {
        System.out.println("알맹이를 내보내고 있습니다.");
        if (--count == 0) {
            System.out.println("더 이상 알맹이가 없습니다.");
            state = SOLD_OUT;
        } else {
            state = NO_QUARTER;
        }
    }
}

 

2. 상태 패턴

  • 객체의 내부 상태가 바뀜에 따라서 객체의 행동이 달라지는 패턴

 

구성 요소

구성 요소 설명
Context
현재 상태를 참조하고 상태별 행동을 위임하는 클래스
State 인터페이스
상태별로 공통으로 제공해야 할 메서드를 정의
ConcreteState 클래스
실제 상태별로 State 인터페이스를 구현한 클래스
상태 전이 메커니즘
상태 클래스 내부에서 다음 상태로 전환

 

장점

장점 설명
조건문 제거
if, switch 없이 상태별 동작을 다형성으로 처리 가능
행동 캡슐화
상태별 행동이 객체로 분리되어 코드가 깔끔하고 명확함
상태 전이 명확
상태 객체가 스스로 다음 상태를 지정 → 전이 흐름이 명시적
유지보수성 향상
새로운 상태 추가 시 기존 코드 변경 최소화 가능 (OCP 적용)
클라이언트가 상태를 몰라도 됨
context가 상태를 관리하므로, 사용자는 상태에 따른 세부 동작을 몰라도 사용 가능

 

3. 예제: GumballMachine

State

  • 상태를 표현할 인터페이스 정의
  • 상태를 표현하는 구현체들을 모두 구현
더보기
public interface State {
    void insertQuarter();
    void ejectQuarter();
    void turnCrank();
    void dispense();
}
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("동전을 넣어주세요."); }
}

 

GumballMachine

더보기
public class GumballMachine {
    State soldOutState, noQuarterState, hasQuarterState, soldState, state;
    int count;
    
    public GumballMachine(int numberGumballs) {
    	soldOutState = new SoldOutState(this);
        noQuarterState = new NoQuarterState(this);
        hasQuarterState = new HasQuarterState(this);
        soldState = new SoldState(this);
    	    	
        this.count = numberGumballs;        
        state = count > 0 ? noQuarterState : soldOutState;
    }
    
    public void insertQuarter() {
    	state.insertQuarter();
    }
    
    public void ejectQuarter() {
    	state.ejectQuarter();
    }
    
    public void turnCrank() {
    	state.turnCrank();
        state.dispense();
    }      
    
    public void releaseBall() {
        System.out.println("알맹이를 내보내고 있습니다.");
        if (count > 0) count--;
    }
}​