행위 패턴 개관: 객체들이 협력하는 방식을 표준화하기
Strategy·Observer·Command·State·Chain of Responsibility 등 행위 패턴이 객체 간 협력과 책임 분리를 어떻게 표준화하는지 정리한다.
2026-08-13 · 최초 발행 2025-10-14
객체들이 협력하는 방식을 정하는 패턴
행위(Behavioral) 패턴은 객체들이 어떤 방식으로 협력해 작업을 수행하는지, 메시지 흐름과 역할을 구조화하는 패턴군이다. 알고리즘 교체, 요청 라우팅, 상태 전이, 이벤트 브로드캐스팅처럼 실행 시점에 동적으로 벌어지는 행위를 안전하게 구성하는 메커니즘을 제공한다. GoF 디자인 패턴의 한 축으로, Strategy·Observer·Command·State·Mediator·Chain of Responsibility·Iterator·Visitor·Template Method·Memento·Interpreter가 여기 속한다.
무엇으로 협력을 구조화하는가
역할 기반 협력
인터페이스·추상화로 역할과 구현을 나누고, 컨텍스트는 역할에만 의존하게 만든다. 의존성 역전과 다형성으로 결합도를 낮추고 테스트도 쉬워진다.
행위 캡슐화와 교체 가능성
알고리즘, 요청 처리, 상태 전이를 객체로 캡슐화하면 런타임에 교체할 수 있다. 기능을 늘릴 때 기존 코드를 거의 건드리지 않아도 되므로 OCP를 지키기 쉬워진다. 조건 분기를 줄이고 캡슐화하면 변경 영향 범위가 30~50% 줄어든다는 추정치도 있다(도메인·팀 성숙도에 따라 변동 가능).
메시지·이벤트 중심 통신
Observer와 Mediator로 발행-구독, 중재자 패턴을 구현한다. 동기/비동기 경계를 명확히 나누면 역흐름(backpressure)과 재시도 정책을 분리하기 쉬워진다.
상태 의존 로직의 분산
State 패턴은 분기문을 줄이고 각 상태 객체에 전이 규칙을 맡긴다. 복잡도가 국소화돼 유지보수성이 좋아지고 회귀 위험도 줄어든다.
패턴 선택부터 결과 반환까지
입력은 도메인 이벤트, API 호출, 스케줄러 트리거다. 처리는 패턴 선택(전략/옵저버/커맨드 등)과 실행 컨텍스트 적용이고, 출력은 성공 결과 또는 실패 시 재시도/보상 트랜잭션/사후 알림이다.
패턴별 적용 지점
도메인 규칙 변경 대응: Strategy
가격·수수료·프로모션 정책을 런타임에 교체하고 A/B 테스트에도 적용한다. 기능 토글·구성 주입(Feature Flag, Config Server)과 묶어서 쓴다. 계약 테스트를 도입해 회귀 리스크를 낮춘다.
이벤트 기반 시스템: Observer, Mediator
주문 → 결제 → 배송 흐름을 느슨한 결합의 이벤트로 구현한다. Mediator로 상호참조를 없애고 이벤트 라우팅 규칙을 한 곳에 모은다. 역압·재시도·중복 제거는 브로커 정책으로 분리한다.
API 커맨드 처리: Command
Command 큐잉으로 스로틀링·재시도·보상 트랜잭션을 표준화한다. 감사 로그와 재현 가능성을 확보하고, 멱등성 키 설계를 함께 챙긴다. 배치를 커맨드로 바꾸면 운영이 일관돼진다.
권한/검증 파이프라인: Chain of Responsibility
인증 → 인가 → 정책 → 데이터 마스킹을 순차 처리한다. 핸들러를 추가·제거하는 것만으로 규정 변경에 대응할 수 있고, 실패 시 중단 정책도 따로 둔다. 감사·추적 같은 공통 크로스컷팅 로직을 끼워 넣기 쉽다.
워크플로 상태 관리: State
주문·티켓·구독의 상태 전이를 캡슐화하고 전이 불변식을 강제한다. UI 상태 머신과 서버 도메인 상태를 맞춰 둔다. 상태별로 타임아웃·보상 작업을 설정한다.
패턴별로 비교하면
| 패턴 | 성능 | 확장성 | 일관성 | 안정성 | 운영 편의 |
|---|---|---|---|---|---|
| Strategy | 오버헤드 낮음, 분기 제거 효과 | 알고리즘 추가 용이 | 컨텍스트 규약으로 행위 일관 | 구현 격리로 회귀 감소 | 구성 기반 교체 용이 |
| Observer | 비동기 시 지연 변동성 | 구독자 수 확장 쉬움 | 최종 일관성 모델 적합 | 분산 상황에서 중복/순서 이슈 | 모니터링/리플레이 필요 |
| Command | 큐잉 비용 존재 | 수평 확장 자연스러움 | 멱등성으로 처리 일관성 | 재시도/보상으로 내고장성 | 감사/재처리 수월 |
| State | 상태 객체 수 증가 | 상태 추가/전이 변경 용이 | 전이 규칙으로 도메인 일관 | 불변식 강제 시 안정성↑ | 디버깅 시 가시화 필요 |
| Chain of Responsibility | 핸들러 체인 길이에 비례 | 핸들러 단위 확장 간단 | 공통 규칙 재사용으로 일관 | 실패-중단 정책 명시 | 구성/순서 관리 중요 |
Strategy 패턴 구현해보면
전제조건: Python 3.11+, 표준 라이브러리만 사용. 정책이 없으면 기본 전략으로 폴백한다.
from typing import Protocol, runtime_checkable, Dict
@runtime_checkable
class PricingStrategy(Protocol):
def apply(self, price: float) -> float: ...
class DefaultPricing:
def apply(self, price: float) -> float:
return round(price, 2)
class SeasonalDiscount:
def __init__(self, rate: float) -> None:
self.rate = rate
def apply(self, price: float) -> float:
return round(price * (1 - self.rate), 2)
class VipDiscount:
def apply(self, price: float) -> float:
return round(price * 0.85, 2)
class PricingContext:
def __init__(self, strategies: Dict[str, PricingStrategy], fallback: PricingStrategy):
self.strategies = strategies
self.fallback = fallback
def quote(self, price: float, key: str) -> float:
strategy = self.strategies.get(key, self.fallback)
return strategy.apply(price)
if __name__ == "__main__":
strategies = {
"seasonal": SeasonalDiscount(rate=0.2),
"vip": VipDiscount(),
}
context = PricingContext(strategies, DefaultPricing())
# 구성값에 따른 동적 선택
print(context.quote(100.0, "vip")) # 85.0
print(context.quote(100.0, "seasonal")) # 80.0
print(context.quote(100.0, "unknown")) # 100.0 (폴백)
운영 팁:
- 전략 키는 구성 서버나 피처 플래그로 주입해 배포 없이 정책을 바꾼다.
- 계약 테스트로 전략 인터페이스 준수를 검증해 회귀를 막는다.
- 관측성(메트릭/로그)을 추가해 전략별 효과와 오류율을 추적한다.
도입할 때 살펴야 할 것들
인터페이스를 먼저 설계해 역할·계약을 고정하고 구현체는 자유롭게 바꾼다. 다만 과도한 추상화는 복잡도와 진입 비용을 키운다.
Observer·Command를 비동기로 만들 때는 멱등성·순서·중복 처리 정책이 필수다. 지연과 최종 일관성이라는 트레이드오프를 받아들여야 한다.
체인·상태·커맨드마다 코릴레이션 ID, 지표, 감사 로그를 표준화해 가시성을 심어 둔다. 다만 관측 비용이 늘어나는 만큼 저장·분석 비용도 함께 고려해야 한다.
전략·핸들러를 구성으로 빼두면 점진적 릴리스와 롤백이 쉬워지지만, 구성 자체의 일관성을 보장하고 검증하는 파이프라인이 필요하다.