// ❌ 나쁜 예: 조건문으로 가득한 코드
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;
public void insertQuarter() {
if (state == HAS_QUARTER) {
System.out.println("You can't insert another quarter");
} else if (state == SOLD_OUT) {
System.out.println("You can't insert a quarter");
} else if (state == SOLD) {
System.out.println("Please wait, already giving you a gumball");
} else if (state == NO_QUARTER) {
state = HAS_QUARTER;
System.out.println("You inserted a quarter");
}
}
// ejectQuarter(), turnCrank(), dispense()도 동일하게 복잡...
}
문제점:
public interface State {
void insertQuarter();
void ejectQuarter();
void turnCrank();
void dispense();
}
왜 Interface인가?
public class GumballMachine {
State currentState; // 현재 상태
public void insertQuarter() {
currentState.insertQuarter(); // 현재 상태에게 위임
}
}
Context의 역할:
공통 구현이 없음
// ✅ 각 상태마다 완전히 다른 구현
public class NoQuarterState implements State {
public void insertQuarter() {
// 동전 삽입 → HasQuarter 상태로 전환
}
}
public class HasQuarterState implements State {
public void insertQuarter() {
// 이미 동전이 있음 → 거부
}
}
상태별 독립적인 동작
느슨한 결합

Context (1) ────────> State (N)
(상태를 가진 객체) (상태별 행동들)
- 현재 상태에 따라 행동이 달라짐
- 상태 전환 시 Context의 동작이 자동으로 변경
- 실행 중 동적으로 상태 변경 가능
핵심 메커니즘:
1. Context가 요청을 받으면
2. 현재 State 객체에게 위임
3. State 객체가 자신의 행동 수행
4. 필요시 Context의 상태 변경 (setState)

// 모든 상태가 구현해야 하는 인터페이스
public interface State {
void insertQuarter(); // 동전 넣기
void ejectQuarter(); // 동전 반환
void turnCrank(); // 손잡이 돌리기
void dispense(); // 검볼 배출
}
public class GumballMachine {
// 모든 가능한 상태들
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State 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;
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;
}
// 검볼 배출
void releaseBall() {
System.out.println("검볼이 나옵니다...");
if (count > 0) {
count--;
}
}
// Getter 메서드들
State getNoQuarterState() { return noQuarterState; }
State getHasQuarterState() { return hasQuarterState; }
State getSoldState() { return soldState; }
State getSoldOutState() { return soldOutState; }
int getCount() { return count; }
}
// 1. NoQuarterState - 동전이 없는 상태
public class NoQuarterState implements State {
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("동전을 넣었습니다");
// 상태 전환: NoQuarter → HasQuarter
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
public void ejectQuarter() {
System.out.println("동전을 넣지 않았습니다");
}
public void turnCrank() {
System.out.println("손잡이를 돌렸지만 동전이 없습니다");
}
public void dispense() {
System.out.println("검볼이 배출되지 않습니다");
}
}
// 2. HasQuarterState - 동전이 있는 상태
public class HasQuarterState implements State {
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("동전은 한 개만 넣을 수 있습니다");
}
public void ejectQuarter() {
System.out.println("동전이 반환됩니다");
// 상태 전환: HasQuarter → NoQuarter
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
public void turnCrank() {
System.out.println("손잡이를 돌렸습니다");
// 상태 전환: HasQuarter → Sold
gumballMachine.setState(gumballMachine.getSoldState());
}
public void dispense() {
System.out.println("검볼이 배출되지 않습니다");
}
}
// 3. SoldState - 검볼 판매 중 상태
public class SoldState implements State {
GumballMachine gumballMachine;
public SoldState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("잠시만 기다려주세요. 검볼이 나오고 있습니다");
}
public void ejectQuarter() {
System.out.println("이미 손잡이를 돌렸습니다");
}
public void turnCrank() {
System.out.println("두 번 돌려도 검볼은 하나만 나옵니다");
}
public void dispense() {
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0) {
// 상태 전환: Sold → NoQuarter
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
System.out.println("검볼이 모두 소진되었습니다!");
// 상태 전환: Sold → SoldOut
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
// 4. SoldOutState - 품절 상태
public class SoldOutState implements State {
GumballMachine gumballMachine;
public SoldOutState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("품절입니다. 동전을 넣을 수 없습니다");
}
public void ejectQuarter() {
System.out.println("동전을 넣지 않았습니다");
}
public void turnCrank() {
System.out.println("품절입니다");
}
public void dispense() {
System.out.println("검볼이 배출되지 않습니다");
}
}
public class GumballMachineTestDrive {
public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine(5);
System.out.println(gumballMachine);
// 테스트 1: 정상 구매
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
// 테스트 2: 동전 넣고 반환
gumballMachine.insertQuarter();
gumballMachine.ejectQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
}
}
초기 상태: 검볼 5개, 상태=NoQuarter
동전을 넣었습니다
손잡이를 돌렸습니다
검볼이 나옵니다...
남은 검볼: 4개, 상태=NoQuarter
동전을 넣었습니다
동전이 반환됩니다
손잡이를 돌렸지만 동전이 없습니다
public class WinnerState implements State {
GumballMachine gumballMachine;
public WinnerState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("잠시만 기다려주세요");
}
public void ejectQuarter() {
System.out.println("이미 손잡이를 돌렸습니다");
}
public void turnCrank() {
System.out.println("두 번 돌릴 수 없습니다");
}
public void dispense() {
System.out.println("🎉 당첨! 검볼 2개가 나옵니다!");
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0) {
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0) {
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
gumballMachine.setState(gumballMachine.getSoldOutState());
}
} else {
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
public class HasQuarterState implements State {
Random randomWinner = new Random();
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void turnCrank() {
System.out.println("손잡이를 돌렸습니다");
int winner = randomWinner.nextInt(10);
// 10% 확률로 Winner 상태로 전환
if ((winner == 0) && (gumballMachine.getCount() > 1)) {
gumballMachine.setState(gumballMachine.getWinnerState());
} else {
gumballMachine.setState(gumballMachine.getSoldState());
}
}
// 나머지 메서드들...
}
새로운 상태 추가의 장점:
| 요소 | 역할 | 특징 |
|---|---|---|
| State Interface | 모든 상태의 공통 메서드 정의 | 느슨한 결합 보장 |
| ConcreteState | 각 상태별 구체적인 행동 구현 | 상태마다 다른 동작 |
| Context | 상태 객체들을 보유하고 위임 | 현재 상태 관리 및 전환 |
| 특징 | State Pattern | Strategy Pattern |
|---|---|---|
| 목적 | 상태에 따른 행동 변경 | 알고리즘 교체 |
| 변경 시점 | 실행 중 자동 변경 | 클라이언트가 선택 |
| 상태 전환 | Context/State가 관리 | 없음 |
| 사용 예 | 검볼 머신, TCP 연결, 문서 상태 | 정렬 알고리즘, 압축 방식 |
// 예: 문서 승인 워크플로우
public interface DocumentState {
void submit(); // 제출
void approve(); // 승인
void reject(); // 거부
void publish(); // 발행
}
// Draft → Submitted → Approved → Published
// 각 상태에서 가능한 동작만 허용
State Pattern은 "객체가 상태 기계(State Machine)처럼 동작해야 할 때" 최고의 선택! 🎯