객체의 행동이 현재 상태에 따라 달라질 때, 상태마다 별도 객체를 두고 현재 상태 객체에 행동을 맡기는 패턴. 상태별 조건문이 메서드마다 반복되는 문제를 푼다.
interface OrderState { pay(o: Order): void; cancel(o: Order): void }
const pending: OrderState = {
pay: o => { o.state = paid },
cancel: o => { o.state = cancelled },
}
const paid: OrderState = {
pay: () => { throw new Error('이미 결제됨') },
cancel: o => { refund(o); o.state = cancelled },
}- 상태가 늘어도 각 상태의 행동이 한 객체에 모여 있어 추가가 쉽다
- 상태 전이 규칙을 상태 객체에 둘지 컨텍스트에 둘지 일관되게 정한다. 상태 객체에 두면 전이가 여러 곳에 흩어져 전체 흐름이 안 보일 수 있다
- 상태가 서너 개뿐이고 규칙이 단순하면 판별 유니언과 switch가 더 읽기 쉽다
상태 기계로 보기
주문의 작성 → 결제 → 배송 → 완료/취소처럼 허용되는 전이가 정해져 있다면 유한 상태 기계(Finite State Machine, FSM)로 그려 보는 것이 먼저다. React에서는 useReducer의 리듀서가 "현재 상태 + 액션 → 다음 상태"를 한곳에 모으는 같은 역할을 한다. 불가능한 상태 조합이 타입으로 표현되지 않게 하는 것도 같은 목적이다.
stateDiagram-v2 state "작성 (pending)" as P state "결제 (paid)" as Pd state "배송" as S state "완료" as Done state "취소 (cancelled)" as C [*] --> P P --> Pd: pay P --> C: cancel Pd --> C: cancel (환불) Pd --> S S --> Done Done --> [*] C --> [*]
전략 패턴과 구조가 같지만, 전략은 바깥에서 골라 끼우고 상태는 객체가 스스로 다음 상태로 넘어간다. 행동 패턴 전체는 행동 패턴.