스택큐힙리스트

State 패턴: if-else 없이 굴러가는 ‘상태 머신’의 힘 본문

개발

State 패턴: if-else 없이 굴러가는 ‘상태 머신’의 힘

스택큐힙리스트 2025. 7. 23. 08:45
반응형

왜 필요한가?

업무 로직이 복잡해질수록 if (status == CREATED) … else if (status == SHIPPED)… 같은 분기문이 폭발한다. 새 상태가 추가될 때마다 코드가 누더기가 되는 건 시간문제다. State 패턴은 객체의 상태를 클래스로 쪼개고, 상태별 동작(행위)을 그 클래스 안에 숨겨 버린다. 그러면 Context(주 객체)는 “지금 내가 어떤 상태인가?”만 알고 있으면 된다. 조건문 대신 상태 객체 교체만으로 행동이 바뀌니, 가독성과 유지보수성이 크게 올라간다.


핵심 개념 한 줄 요약

“상태를 클래스로 캡슐화해, 객체의 행동을 런타임에 교체한다.”
Context ↔ State 인터페이스 ↔ ConcreteState 들이 만드는 삼각 구도가 전부다.


Kotlin 예제 — 주문 상태 흐름

// 1) 상태 인터페이스
interface OrderState {
    fun next(order: Order)
    fun cancel(order: Order)
}

// 2) 구체 상태
object Created : OrderState {
    override fun next(order: Order) { order.state = Shipped }
    override fun cancel(order: Order) { order.state = Cancelled }
}

object Shipped : OrderState {
    override fun next(order: Order) { order.state = Delivered }
    override fun cancel(order: Order) = println("이미 배송 중이라 취소 불가")
}

object Delivered : OrderState {
    override fun next(order: Order) = println("이미 배송 완료")
    override fun cancel(order: Order) = println("반품 절차 필요")
}

object Cancelled : OrderState {
    override fun next(order: Order) = println("취소된 주문")
    override fun cancel(order: Order) = println("이미 취소됨")
}

// 3) 컨텍스트
class Order(var state: OrderState = Created) {
    fun next()   = state.next(this)
    fun cancel() = state.cancel(this)
}

// 사용 예
val order = Order()
order.next()    // Created → Shipped
order.cancel()  // 배송 중이라 취소 불가

객체를 갈아끼우는 것만으로 상태 전이가 끝난다. State 클래스들이 단일 책임을 지키기 때문에 새 단계(예: Returned)를 추가해도 기존 코드는 손대지 않는다.


언제 쓸까요?

  • 주문·결제·배송처럼 상태 전이가 명확한 도메인
  • 게임 캐릭터(대기→공격→피격) 같은 상태머신 구현
  • 네트워크 커넥션(연결 시도→연결됨→종료) 관리
  • 리팩터링: 거대한 switch 문을 객체지향적으로 분리할 때

실전 팁

  1. enum + State 혼합
    상태 개수를 enum으로 관리하면서, enum에 전략 메서드를 붙여 가볍게 구현할 수도 있다.
  2. Spring StateMachine
    복잡한 전이 조건과 Guard 로직이 많다면, OSS 라이브러리로 도표 기반 정의를 추천한다.
  3. 디버깅
    전이 로그를 남기면 QA 단계에서 “어디서 막혔는지” 한눈에 보인다.
반응형
Comments