[Design Pattern] State Pattern

younghyun·2022년 10월 26일
0

Design Pattern

목록 보기
12/14
post-thumbnail

State Pattern 이란

객체의 내부 상태가 바뀔 때 객체의 동작을 변경할 수 있도록 하여 객체는 자신의 클래스를 바꾸는 것과 같은 결과를 얻는 패턴으로, 상태를 한 곳에서 관리하여 변경을 최소화할 수 있다는 장점이 있다.

설계

image
  • Context
    • 여러 가지 상태를 가지고 있으며 각 상태에 따라 동작이 수행되도록 구현
  • State
    • 모든 상태가 구현해야 할 공통 인터페이스를 정의
  • ConcreateState
    • State를 구현한 클래스
    • 각 상태에 따라 다른 요청을 처리하는 방법을 구현

예시

뽑기 기계

  • 상태
    • 동전 없음
    • 동전 있음
    • 알맹이 매진
    • 알맹이 판매
  • 기능
    • 동전 투입
    • 동전 반환
    • 손잡이 돌림
    • 알맹이 내보냄
image

1. State 패턴을 활용

기능 변경사항이 있을 시, 코드를 대폭 수정해야 함
➜ 상태를 각각의 클래스로 만들어서 기능 구현을 위임

2. 클래스 구성

image
  • 모든 행동에 대한 메소드가 들어있는 인터페이스 정의
  • 상태 한 개가 클래스의 형태

3. 실제 구현

Bad Case

GumbalMachine

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("동전이 이미 들어있습니다.");
        } else if (state == SOLD_OUT) {
            System.out.println("매진되었습니다.");
        } else if (state == SOLD) {
            System.out.println("캡슐이 나가고 있습니다. 기다려 주세요.");
        } else if (state == NO_QUARTER) {
            state = HAS_QUARTER;
            System.out.println("동전이 투입되었습니다.");
        }
    }

    // 동전 반환
    public void ejectQuarter() { 
               ...
    }
    
    // 손잡이 돌림
    public void turnCrank() {
               ...
    }

    // 알맹이 내보냄
    public void dispense() {
               ...
    }
}
  • 관리하기 힘든 if문들의 제거가 필요함
  • 새로운 상태 클래스를 추가하는 확장에 대해서 닫혀있음

Good Case

State 인터페이스 만들기 (공통 기능들을 다룸)

public interface State {

    void insertQuarter();   // 동전 투입
    void ejectQuarter();   // 동전 반환
    void turnCrank();    // 손잡이 돌림
    void dispense();    // 알맹이 내보냄
}

GumbalMachine

public class GumballMachine {

    // State 클래스로 상태 표시
    State soldState;         // 알맹이 판매
    State soldOutState;      // 알맹이 매진
    State noQuarterState;    // 동전 없음
    State hasQuarterState;   // 동전 있음

    State state = soldOutState;     // 현재 상태
    int count = 0;   // 알맹이 개수

    public GumballMachine(int numberOfGumballs) {   // 상태 객체 사용
    
        soldState = new SoldState(this);
        soldOutState = new SoldOutState(this);
        noQuarterState = new NoQuarterState(this);
        hasQuarterState = new HasQuarterState(this);
        
        this.count = numberOfGumballs;
        if (numberOfGumballs > 0) {
            state = noQuarterState;
        }
    }

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

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

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

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

    public void releaseBall() {
        System.out.println("A gumball comes rolling out the slot...");
        if (count != 0) {
            count -= 1;
        }
    }

    // 각 상태에 대한 게터 메서드 및 기타 메서드
}

SoldState

public class SoldState implements State {

    GumballMachine gumballMachine;

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

    @Override
    public void insertQuarter() {
        System.out.println("캡슐이 나가고 있습니다. 기다려 주세요.");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("이미 캡슐을 뽑으셨습니다.");
    }

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

    @Override
    public void dispense() {
        gumballMachine.releaseBall();
        
        if (gumballMachine.getCount() > 0) {   // 알맹이가 더 있다면,
            gumballMachine.setState(gumballMachine.getNoQuarterState());  // '동전 없는 상태'로 변환
        } else {
            System.out.println("더 이상 캡슐이 없습니다.");
            gumballMachine.setState(gumballMachine.getSoldOutState());   // '알맹이 매진'로 변환
        }
    }
}

WinnerState (열 번에 한 번 알맹이를 한 개 더 주는 기능 추가)

public class WinnerState implements State {

    GumballMachine gumballMachine;
    
    public WinnerState(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() {      // 이미 알맹이가 두개 이상일 때 올 수 있는 상태임
        System.out.println("축하드립니다. 알맹이를 한 개 더 받으실 수 있습니다");
        gumballMachine.releaseBall();
        gumballMachine.releaseBall();
        if (gumballMachine.getCount() > 0) {
            gumballMachine.setState(gumballMachine.getNoQuarterState());
        } else {
            System.out.println("더 이상 알맹이가 없습니다");
            gumballMachine.setState(gumballMachine.getSoldOutState());
        }
    }
}

HasQuarterState 수정

public class HasQuarterState implements State {
    // 변경된 부분만 보임. 나머지 코드는 같음
    Random random = new Random(System.currentImeMillis());
    public void turnCrank() {
        System.out.println("손잡이를 돌리셨습니다");
        int winner = random.nextInt(10);
        if ((winner == 0) && (gumballMachine.getCount() > 1)) {    // 남은 알맹이가 두개 이상일 때,
            gumballMachine.setState(gumballMachine.getWinnerState());   // winner 상태로 전환
        } else {                                     
            // 아니라면, 판매 상태로 전환 (알맹이 한 개 or 0개일 때를 다룬 상황)
            gumballMachine.setState(gumballMachine.getSoldState());  
        }
    }
} 
profile
🌱 주니어 백엔드 개발자입니당

0개의 댓글