상태를 객체로 만들어, 상태가 바뀌면 객체의 행동이 통째로 바뀌게 하는 패턴.
무엇을 푸는가 — 조건문 폭발
// Before — 상태가 늘 때마다 모든 메서드를 고쳐야 한다
void publish() {
if (state == DRAFT) { state = MODERATION; }
else if (state == MODERATION) { if (isAdmin) state = PUBLISHED; }
else if (state == PUBLISHED) { /* 아무것도 안 함 */ }
}
void render() { if (state == DRAFT) ... else if ... } // 같은 분기가 반복된다
// After — 상태마다 클래스
interface State { void publish(Document doc); }
class Draft implements State {
public void publish(Document d) { d.setState(new Moderation()); }
}
class Moderation implements State {
public void publish(Document d) { if (d.isAdmin()) d.setState(new Published()); }
}
class Published implements State {
public void publish(Document d) { /* 최종 상태 */ }
}
class Document {
private State state = new Draft();
void publish() { state.publish(this); } // 분기가 사라진다
}
Strategy와의 차이 (★ 가장 많이 묻는다)
구조는 거의 같다 — 인터페이스 + 여러 구현 + 위임 다른 것은 '누가 바꾸고, 서로를 아는가' 다
-
Strategy — 클라이언트가 밖에서 골라 넣는다
- 전략끼리 서로를 모른다 (독립적·대체 가능)
- "어떻게 할지" 를 고른다 — 정렬 알고리즘, 결제 수단
-
State — 객체가 스스로 다음 상태로 바꾼다
- 상태끼리 전이 관계를 안다 (Draft 는 다음이 Moderation 임을 안다)
- "지금 무엇인지" 에 따라 행동이 달라진다 — 주문 상태, 문서 워크플로
대가
- 상태가 적고 전이가 단순하면 클래스만 늘어난다 (enum + switch 로 충분)
- 전이 규칙이 각 상태에 흩어져 전체 흐름을 한눈에 보기 어렵다
- 전이표를 문서나 다이어그램으로 따로 유지하는 것이 좋다
실무에서
주문 상태 (결제대기 → 결제완료 → 배송중 → 배송완료 → 취소/반품) 문서 워크플로 (초안 → 검토 → 승인 → 게시) TCP 연결 상태 · Spring StateMachine
면접 함정
- ❌ "Strategy와 같다" → 구조가 같아도 누가 전환하는가가 다르다.
- ❌ 상태 2개짜리에 도입 →
boolean이나enum이 더 읽기 쉽다.
전이를 어디에 둘 것인가
-
방식 A — 각 상태가 다음 상태를 안다
- 장점 상태별 규칙이 응집된다 / 단점 전체 전이도를 한눈에 못 본다
-
방식 B — 컨텍스트나 전이표가 규칙을 갖는다
- 장점 전이 전체가 한 곳에 보인다 / 단점 상태가 늘면 표가 커진다
-
실무에서는 전이도를 문서로 유지하며 A 를 쓰는 경우가 많다
enum으로도 만들 수 있다
enum OrderState {
PAID { OrderState next() { return SHIPPING; } },
SHIPPING { OrderState next() { return DELIVERED; } },
DELIVERED{ OrderState next() { return this; } };
abstract OrderState next();
}
상태가 고정이고 상태별 데이터가 없으면 enum이 간결하다. 상태마다 필드·의존성이 필요하면 클래스로 간다.
상태를 어디에 저장하나
DB 에는 상태 '이름'(문자열·코드) 을 저장하고 로드 시 State 객체로 복원한다
이때 저장된 상태 값이 곧 외부 계약이 된다
- 상태 이름을 바꾸면 마이그레이션이 필요하다