
Mahdi Shamlou | 행동 디자인 패턴 (Behavioral Design Patterns) 2026: Strategy, Observer
요약
객체 간의 상호작용과 책임 분산에 초점을 맞춘 행동 디자인 패턴(Behavioral Design Patterns)을 소개합니다. Strategy, Observer 등 주요 패턴의 개념과 필요성을 설명하며 효율적인 소프트웨어 설계 방법을 다룹니다.
핵심 포인트
- 행동 패턴은 객체 간의 통신과 협력 방식을 정의함
- 강한 결합도와 복잡한 제어 흐름 문제를 해결할 수 있음
- Strategy 패턴은 런타임에 알고리즘을 교체 가능하게 함
- Observer 패턴은 상태 변경 시 여러 객체에 알림을 전달함
Mahdi Shamlou입니다.
여러분은 **생성 패턴 (Creational Patterns)**을 마스터했습니다. 객체를 어떻게 구축하는지 이해하고 있습니다.
여러분은 **구조 패턴 (Structural Patterns)**을 정복했습니다. 객체 간의 관계를 어떻게 조직화하는지 알고 있습니다.
이제 마지막 조각인 차례입니다: 행동 디자인 패턴 (Behavioral Design Patterns).
여기서부터 흥미로워집니다.
행동 패턴은 소프트웨어 설계에서 가장 어려운 질문에 답합니다:
객체들이 어떻게 통신하고 협력해야 하는가?
저는 다음과 같은 시스템들을 보았습니다:
- 객체들이 강하게 결합(tightly coupled)되어 있어 변경이 불가능함
- 상태 관리(State management)가 사방에 흩어져 있음
- 새로운 동작을 추가하려면 수십 개의 클래스를 수정해야 함
- 이벤트 처리(Event handling)가 혼란스럽고 유지보수가 불가능함
- 제어 흐름(Control flow)이 복잡한 조건문 속에 파묻혀 있음
적절한 행동 패턴을 적용하면 이러한 문제 대부분이 사라집니다.
주니어 개발자와 시니어 개발자의 차이는 종종 객체 간의 행동과 통신을 얼마나 잘 관리하느냐에서 갈립니다.
그들을 구분 짓는 패턴들을 자세히 살펴보겠습니다.
행동 디자인 패턴이란 무엇인가?
행동 패턴은 객체가 어떻게 상호작용하고 책임을 분산하는지에 초점을 맞춥니다.
생성 패턴이 _"객체를 어떻게 생성할 것인가?"_라고 묻고,
구조 패턴이 _"객체를 어떻게 조직화할 것인가?"_라고 묻는다면,
행동 패턴은 _"객체들이 서로 어떻게 대화하는가? 누가 무엇을 하는가?"_라고 묻습니다.
행동 패턴은 다음과 같습니다:
- Strategy — 런타임에 알고리즘 선택
- Observer — 상태 변경에 대해 여러 객체에 알림
- Command — 요청을 객체로 캡슐화
- State — 내부 상태에 따라 동작 변경
- Template Method — 알고리즘 구조를 정의하고 세부 사항은 서브클래스에서 구현
- Chain of Responsibility — 핸들러를 통해 요청을 전달
- Iterator — 구현 세부 사항을 노출하지 않고 컬렉션 순회
- Mediator — 객체 간의 통신을 중앙 집중화
- Memento — 객체의 상태를 저장하고 복구
- Visitor — 기존 클래스를 변경하지 않고 새로운 연산 추가
- Interpreter — 커스텀 언어 또는 문법을 정의하고 평가
실제 프로덕션 코드를 통해 각각을 살펴보겠습니다.
중요한 행동 패턴 (Behavioral Patterns)
1. Strategy 패턴
Strategy 패턴은 알고리즘 제품군을 정의하고, 각 알고리즘을 캡슐화하여 서로 교체 가능하게 만듭니다.
이를 통해 알고리즘을 이를 사용하는 클라이언트와 독립적으로 변경할 수 있습니다.
결제 시스템을 상상해 보세요. 다음과 같은 결제 수단을 지원합니다:
- 신용카드 결제
- PayPal
- Stripe
- Bitcoin
Strategy 패턴이 없다면, 결제 코드(checkout code)는 거대한 조건문이 됩니다:
if payment_method == "credit_card":
# 50줄의 신용카드 로직
elif payment_method == "paypal":
...
Strategy 패턴을 사용하면, 각 결제 수단은 별개의 교체 가능한 알고리즘이 됩니다.
사용 시점:
- 하나의 작업에 대해 여러 알고리즘이 필요한 경우
- 다양한 결제 수단
- 압축 알고리즘 (zip, gzip, rar)
- 정렬 알고리즘 (quicksort, mergesort, bubblesort)
- 데이터 내보내기 형식 (JSON, CSV, XML)
- 가격 책정 전략 (standard, premium, enterprise)
- 검색/필터링 전략
나쁜 코드 (패턴 미사용)
class PaymentProcessor:
def process_payment(self, amount, method):
if method == "credit_card":
...
문제점:
- 거대한 조건문 로직
- 각 전략을 테스트하기 어려움
- 새로운 전략을 추가하기 어려움
- 단일 책임 원칙 (Single Responsibility Principle) 위반
좋은 코드 (Strategy 사용)
from abc import ABC, abstractmethod
# Strategy 인터페이스
...
장점 (Pros):
- 새로운 알고리즘을 추가하기 쉬움
- 거대한 조건문 (conditional statements)을 제거함
- 알고리즘을 서로 교체 가능하게 만듦
- 각 전략 (strategy)을 독립적으로 테스트할 수 있음
- 단일 책임 원칙 (Single Responsibility Principle)을 준수함
단점 (Cons):
- 관리해야 할 클래스가 많아짐
- 단순한 사례에서는 오버헤드 (overhead)가 발생함
- 클라이언트가 어떤 전략을 선택할지 알아야 함
나의 견해 (My Take):
Strategy는 실제 프로덕션 시스템에서 가장 실용적인 패턴 중 하나입니다.
거대한 if-elif-elif 체인을 볼 때마다 Strategy를 떠올리세요. 그것은 거의 항상 정답입니다.
결제 수단, 내보내기 형식 (export formats), 정렬 알고리즘 (sorting algorithms), 캐싱 전략 (caching strategies) — 이 모든 것들이 Strategy의 완벽한 사용 사례입니다.
핵심 통찰: 무언가를 수행하는 서로 다른 방법들 사이에서 선택해야 한다면, Strategy가 당신의 패턴입니다.
2. Observer 패턴
Observer 패턴은 객체 간의 일대다 (one-to-many) 의존성을 정의하여, 한 객체의 상태가 변경될 때 그에 의존하는 모든 객체에 자동으로 알림을 보냅니다.
주식 티커 (stock ticker)를 생각해 보세요. 주가가 변하면 여러 트레이더, 모니터, 알림 시스템이 즉시 알아야 합니다.
Observer가 없다면, 주식 객체는 모든 트레이더, 모니터, 알림 시스템에 대해 알고 있어야 합니다 (강한 결합, tight coupling).
Observer를 사용하면, 주식 객체는 단지 관심 있는 대상에게 알림을 보낼 뿐입니다.
사용 시기:
- 이벤트 시스템 (UI 버튼, 입력 필드)
- 주식 티커 시스템
- 실시간 알림
- MVC 아키텍처 (모델의 변경이 뷰에 알림을 보냄)
- 발행/구독 (Pub/Sub) 시스템
- 이벤트 기반 아키텍처 (Event-driven architecture)
- 소셜 미디어 (사용자 팔로우)
나쁜 코드 (패턴 미사용)
class Stock:
def __init__(self, symbol, price):
self.symbol = symbol
...
문제점:
- Stock이 모든 옵저버 (observer)와 강하게 결합되어 있음
- 새로운 옵저버 유형을 추가하려면 Stock을 수정해야 함
- 테스트하기 어려움
- 단일 책임 원칙 (Single Responsibility) 위반
좋은 코드 (Observer 사용)
from abc import ABC, abstractmethod
# Observer 인터페이스
...
실제 사례: 이벤트 시스템
class Button:
def __init__(self):
self._listeners = []
...
장점 (Pros):
- Subject (주체)와 Observer (옵저버) 간의 느슨한 결합 (Loose coupling)
- 동적인 구독/구독 해제 (Dynamic subscription/unsubscription)
- 다수의 옵저버 지원
- 단일 책임 원칙 (Single Responsibility) 준수
- 브로드캐스트 통신 (Broadcast communication) 가능
단점 (Cons):
- 옵저버의 순서를 예측할 수 없음
- 이벤트 체인 (Event chains)을 디버깅하기 어려울 수 있음
- 옵저버가 많아질 경우 성능 오버헤드 (Performance overhead) 발생
나의 견해 (My Take):
Observer 패턴은 현대적인 이벤트 기반 시스템 (Event-driven systems)의 근간입니다.
여러분은 다음과 같은 곳에서 이를 끊임없이 사용하고 있습니다:
- 이벤트 핸들러 (Event handlers)를 사용하는 React/Vue
- 모델 변경을 위한 Django signals
- 알림을 위한 Webhook 시스템
- 마이크로서비스 (Microservices)의 이벤트 버스 (Event buses)
핵심 통찰: 하나의 요소가 변경될 때 이에 반응해야 하는 요소가 많다면, Observer 패턴을 사용하세요.
객체들이 서로를 직접 호출하게 만들지 마세요. 대신 각 객체가 독립적으로 관찰하고 반응하게 하세요.
3. Command 패턴 (Command Pattern)
Command 패턴은 요청을 하나의 객체로 캡슐화(Encapsulate)합니다. 이를 통해 클라이언트에게 서로 다른 요청을 매개변수화하여 전달하거나, 요청을 큐(Queue)에 넣거나, 요청을 로그(Log)로 남길 수 있습니다.
이 패턴은 동작을 독립적인 객체로 변환합니다.
텍스트 에디터의 실행 취소/다시 실행 (Undo/Redo) 기능을 생각해 보세요. 모든 동작(텍스트 삭제, 텍스트 삽입, 서식 지정)은 하나의 Command 객체입니다.
실행 취소를 하려면 스택(Stack)에서 마지막 명령을 꺼내어(Pop) 그 반대 동작을 수행하기만 하면 됩니다.
사용 시점:
- 실행 취소/다시 실행 (Undo/Redo) 기능
- 작업 큐잉 (Task queuing) 및 스케줄링
- 매크로 녹화 (Macro recording)
- 비동기 작업 실행 (Asynchronous task execution)
- 트랜잭션 관리 (Transaction management)
- 요청 로깅 (Request logging)
- 콜백 시스템 (Callback systems)
나쁜 코드 (패턴 미사용)
class TextEditor:
def __init__(self):
self.text = ""
...
좋은 코드 (Command 패턴 사용)
from abc import ABC, abstractmethod
# Command 인터페이스
...
장점 (Pros):
- 송신자(Sender)와 수신자(Receiver)의 결합도 해제
- 실행 취소/다시 실행 (Undo/Redo) 구현이 매우 간단해짐
- 명령(Command)을 큐에 넣거나 스케줄링하기 용이함
- 명령 이력(Command history)을 로깅하기 쉬움
- 매크로 및 복합 명령 (Composite commands) 지원
단점 (Cons):
- 각 명령마다 더 많은 클래스가 필요함
- 명령을 저장하기 위한 메모리 오버헤드 (Memory overhead)
- 명령이 많아지면 복잡도가 높아질 수 있음
나의 견해 (My Take):
Command 패턴은 실행 취소/재실행 (undo/redo), 큐잉 (queuing), 또는 트랜잭션 관리 (transaction management)가 필요한 모든 시스템에 필수적입니다.
이 패턴의 탁월함은: 각 액션이 스스로를 어떻게 실행하고 어떻게 실행 취소하는지 알고 있다는 점에 있습니다.
중앙 집중식 실행 취소 로직이 필요하지 않습니다. 각 명령 (command)이 자신의 역행 (reversal)을 스스로 책임집니다.
이것이 전문적인 텍스트 에디터, 디자인 도구, 그리고 버전 관리 시스템 (version control systems)이 작동하는 방식입니다.
4. State 패턴
State 패턴은 객체의 내부 상태 (internal state)가 변경될 때 객체의 행동을 변경할 수 있게 해줍니다.
마치 객체가 자신의 클래스를 변경하는 것처럼 보일 것입니다.
신호등을 생각해 보세요:
빨간불 → 초록불 (직접 노란불로 갈 수 없음)
초록불 → 노란불 (직접 빨간불로 갈 수 없음)
노란불 → 빨간불 (직접 초록불로 갈 수 없음)
State 패턴이 없다면, 거대한 조건문 (conditionals)을 갖게 됩니다:
if self.state == "red":
# 빨간불 행동
elif self.state == "yellow":
...
State 패턴을 사용하면, 각 상태 (state)는 자신이 무엇을 할 수 있는지 알고 있는 별개의 객체가 됩니다.
사용 시점:
- 상태 머신 (State machines) (신호등, 주문, 워크플로우)
- UI 상태 관리 (UI state management)
- 게임 상태 (Game states) (메뉴, 플레이 중, 일시 정지, 게임 오버)
- 주문 상태 (Order status) (대기 중, 처리 중, 배송됨, 배달 완료)
- 연결 상태 (Connection states) (연결 중, 연결됨, 연결 끊김)
- 사용자 상태 (User states) (로그아웃됨, 로그인됨, 관리자)
나쁜 코드 (패턴 미사용)
class TrafficLight:
def __init__(self):
self.state = "red"
def next(self):
if self.state == "red":
self.state = "green"
print("🟢 Green light")
elif self.state == "green":
self.state = "yellow"
print("🟡 Yellow light")
elif self.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기