상태 패턴, 조건문 분기를 상태 클래스로 옮기기
상태 패턴은 상태에 따라 달라지는 행동을 각 상태 클래스로 분리해, 상태가 늘 때마다 커지는 if·switch 분기를 없애는 GoF 행동 패턴이다. 구조와 전략 패턴과의 차이, 도입 판단 기준을 코드로 정리했다.
상태 패턴(State Pattern)은 객체의 내부 상태에 따라 행동이 달라질 때, 그 상태별 행동을 각각 별도 클래스로 분리해 조건문 없이 갈아 끼우는 설계 방식이다. 상태가 하나 늘 때마다 여기저기서 커지던 if·switch 분기를, 상태 클래스 하나를 추가하는 것으로 바꾼다. GoF가 저서 Design Patterns에서 정리한 23개 패턴 중 행동(behavioral) 패턴에 속하며, 디자인 패턴 정리 사이트 refactoring.guru는 이를 “객체가 마치 자기 클래스를 바꾸는 것처럼 보이게 하는 패턴”으로 설명한다. 이 글은 상태 패턴의 구조와 전략 패턴과의 차이, 도입 판단 기준까지만 다루고, 특정 언어·프레임워크의 구현체는 다루지 않는다.
상태 패턴이 푸는 문제는 무엇인가
상태에 따라 행동이 갈리는 코드가 조건문으로 뒤덮이는 문제를 푼다. 문서 발행 흐름을 예로 들어 보자. 초안(draft) → 검토(moderation) → 발행(published) 세 상태가 있고, 각 상태에서 발행·반려·수정 같은 행동이 다르게 동작한다. 상태를 문자열이나 열거형으로 두고 분기하면 행동 메서드마다 같은 조건 사다리가 반복된다.
def publish(doc):
if doc.status == 'draft':
doc.status = 'moderation'
elif doc.status == 'moderation':
doc.status = 'published'
elif doc.status == 'published':
raise Error('이미 발행된 글')행동이 하나 늘 때마다 이런 메서드가 하나씩 생기고, 상태가 하나 늘면 모든 메서드의 사다리를 열어 elif를 끼워 넣어야 한다. 상태 수와 행동 수의 곱만큼 분기가 흩어지니, 새 상태를 넣다가 어느 한 메서드에서 처리를 빠뜨리는 실수가 나기 쉽다.
상태를 클래스로 나눠 행동을 위임한다
해법은 상태마다 클래스를 만들고, 문맥(Context) 객체가 현재 상태 객체에 행동을 넘기는 것이다. 구성은 세 부분이다. 공통 인터페이스인 State, 그 인터페이스를 구현한 구체 상태 클래스들, 그리고 현재 상태 객체를 들고 있는 Context다. Context는 요청이 오면 스스로 판단하지 않고 지금 붙어 있는 상태 객체에 그대로 위임하고, 상태를 바꿀 때는 참조하는 상태 객체를 다른 상태 객체로 교체한다.
class State:
def publish(self, doc): ...
class Draft(State):
def publish(self, doc):
doc.state = Moderation() # 초안 → 검토
class Moderation(State):
def publish(self, doc):
doc.state = Published() # 검토 → 발행
class Document: # Context
def __init__(self):
self.state = Draft()
def publish(self):
self.state.publish(self)이제 각 상태의 행동과 다음 상태로의 전이는 그 상태 클래스 안에만 있다. 새 상태를 추가하려면 State를 구현한 클래스를 하나 더 만들면 되고, 기존 상태 클래스는 건드리지 않는다. 조건 분기가 사라진 자리에 다형성이 들어선 셈이다. 상태와 전이를 각각 노드와 화살표로 보는 시각은 유한 상태 기계(FSM, 정해진 상태들 사이를 규칙대로 오가는 모델)와 같은데, 흐름을 노드·엣지로 제어하는 발상은 AI 에이전트를 그래프로 오케스트레이션하는 방식과도 통한다.

전략 패턴과 무엇이 다른가
구조는 거의 같지만 의도가 다르다. 전략 패턴(Strategy)과 상태 패턴은 둘 다 GoF 행동 패턴이고, 인터페이스에 맞춘 객체를 갈아 끼운다는 뼈대가 같다. refactoring.guru도 두 패턴의 클래스 다이어그램이 사실상 같다고 짚는다. 차이는 누가, 왜 갈아 끼우는가에 있다.
- 전략 패턴: 바깥에서 알고리즘을 골라 넣는다. 정렬 방식이나 결제 수단을 상황에 맞게 교체하며, 전략끼리는 서로를 모른다.
- 상태 패턴: 안에서 상태가 스스로 다음 상태로 넘어간다. 검토 상태가 발행 요청을 받으면 자신을 발행 상태로 바꾸는 식이라, 상태끼리 전이 관계를 안다.
교체를 클라이언트가 주도하면 전략, 객체가 자기 생명주기를 따라 스스로 주도하면 상태다.
언제 쓰고 언제 피하나
상태가 두세 개 이하이고 전이가 단순하면 굳이 도입하지 않는다. 상태 하나에 if 한 줄이면 끝나는 코드를 클래스 여러 개로 쪼개면 오히려 파일만 늘고 읽기 어려워진다. 열거형과 switch 한 곳으로 충분하다. 반대로 상태가 늘고 상태마다 행동이 뚜렷이 갈리며, 전이 규칙이 조건문 사다리로 번져 한눈에 안 들어오기 시작하면 그때가 도입 시점이다.
- 도입이 어울리는 신호: 같은
switch (status)가 여러 메서드에 중복되고, 상태를 추가할 때마다 여러 곳을 함께 고쳐야 할 때. - 미루는 게 나은 경우: 상태가 고정적이고 전이가 한두 갈래뿐이거나, 상태별 행동 차이가 거의 없을 때.
상태 패턴은 분기의 복잡도를 클래스 구조로 옮겨 담는 거래다. 옮겨 담을 복잡도가 있을 때 이득이고, 없으면 비용만 남는다.





