객체의 내부 상태가 바뀔 때 객체의 동작을 변경할 수 있도록 하여 객체는 자신의 클래스를 바꾸는 것과 같은 결과를 얻는 패턴으로, 상태를 한 곳에서 관리하여 변경을 최소화할 수 있다는 장점이 있다.
Context
State
ConcreateState
뽑기 기계
기능 변경사항이 있을 시, 코드를 대폭 수정해야 함
➜ 상태를 각각의 클래스로 만들어서 기능 구현을 위임
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() {
...
}
}
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());
}
}
}