개발블로그

[디자인패턴] 옵저버 패턴 본문

STUDY/CS

[디자인패턴] 옵저버 패턴

devmel 2026. 9. 4. 12:46
Contents 접기
 

개념

 

필요성

프로그램을 만들다 보면 어떤 객체의 상태 변화가 여러 기능에 영향을 주는 경우가 있다.

 

EX) 주문 상태가 결제완료로 바뀌었다고 할 때, 단순히 주문 상태만 바꾸면 끝나지 않음.

=> 상태 변화에 따라 여러 후속 작업이 필요할 수 있음

ex) 알림발송, 재고차감, 배송 준비, 포인트 적립...

def complete_order(order):
    order.status = "paid"

    send_notification(order)
    decrease_stock(order)
    prepare_delivery(order)
    add_point(order)
    
# 상태 변화에 반응해야 하는 기능이 계속 늘어나면, 주문 로직은 점점 많은 일을 알게 됨
# ex) 나중에 쿠폰 사용 처리, 리뷰 요청 알림, 판매자 정산 요청이 추가되면, complete_order()는 계속 수정되어야 함
# 문제는, 주문 상태를 변경하는 코드가 상태 변화 이후에 실행될 모든 후속 작업까지 직접 알고 있어야 함.

 

옵저버 패턴은 이런 상황에서 사용할 수 있다

" 상태가 바뀌었을 때, 그 변화를 필요로 하는 객체들에게 어떻게 알릴까? "

 

의미

어떤 객체의 상태 변화가 발생했을 때, 그 변화를 필요로 하는 객체들에게 자동으로 알리는 패턴.

상태가 바뀌는 객체는 자신에게 관심 있는 객체들을 등록해두고, 변화가 발생하면 등록된 객체들에게 알린다.

+ 상태 변화에 반응하는 여러 작업을 느슨하게 연결하되, 실행 흐름과 실패 처리를 함께 관리해야 함 

 

구분 역할 예시
Subject 상태 변화를 발생시키는 객체
=> 상태 변화와 Observer 등록/알림을 관리
주문
Observer 상태 변화를 전달받는 객체
=> 알림을 받았을 때 수행할 동작을 정의
알림 서비스, 재고 서비스, 배송 서비스, 포인트 서비스
Notify Subject가 Observer들에게 변화를 알리는 동작
=> 실제 후속 작업을 수행
주문 결제 완료 사실을 전달

 

 


 

예시

 

EX] 주문 상태가 결제 완료로 변경되면 여러 후속 작업이 필요할 때

처음에는 주문 완료 함수 안에서 필요한 기능을 직접 호출할 수 있음

def complete_order(order):
    order.status = "paid"

    send_notification(order)
    decrease_stock(order)
    prepare_delivery(order)
    add_point(order)

흐름이 단순할 때는 이해하기 쉬움. 

주문 상태를 변경하고, 필요한 후속 작업을 순서대로 실행함

 

결제 완료 이후 해야 할 일이 늘어나면 문제가 생김

def complete_order(order):
    order.status = "paid"

    send_notification(order)
    decrease_stock(order)
    prepare_delivery(order)
    add_point(order)
    request_review(order)
    create_settlement(order)

주문 상태 변경 뿐 아니라 알림, 재고, 배송, 포인트, 리뷰, 정산까지 직접 알고 있음.

문제는 상태 변화 이후의 후속 작업이 모두 주문 로직이 붙는다.

 

문제 설명
책임 증가 주문 완료 로직이 여러 후속 작업까지 직접 담당한다.
수정 범위 증가 후속 작업이 추가될 때마다 주문 완료 코드를 수정해야 한다.
결합도 증가 주문 로직이 알림, 재고, 배송, 정산 기능을 직접 알게 된다.
테스트 어려움 주문 상태 변경만 테스트하고 싶어도 여러 후속 작업이 함께 얽힌다.

 

옵저버로 알림 책임 분리하기

 

먼저, 상태 변화를 전달받을 옵저버의 공통 형태를 정함

class OrderObserver:
    def update(self, order):
        pass

 

각 후속 작업은 옵저버로 구현

class NotificationObserver(OrderObserver):
    def update(self, order):
        return "알림 발송"

class StockObserver(OrderObserver):
    def update(self, order):
        return "재고 차감"

class DeliveryObserver(OrderObserver):
    def update(self, order):
        return "배송 준비"

 

이제 주문 객체는 옵저버를 등록하고, 상태가 변경되었을 때 알림

class Order:
    def __init__(self):
        self.status = "created"
        self.observers = []

    def add_observer(self, observer):
        self.observers.append(observer)

    def notify(self):
        for observer in self.observers:
            observer.update(self)

    def complete(self):
        self.status = "paid"
        self.notify()

 

사용하는 코드는 필요한 옵저버를 등록한다. 

order = Order()

order.add_observer(NotificationObserver())
order.add_observer(StockObserver())
order.add_observer(DeliveryObserver())

order.complete()

Order은 결제 완료 이후 어떤 세부 작업이 실행되는지 직접 구현하지 않음. 

등록된 옵저버들에게 상태 변화를 알린다. 

 

새로운 후속 작업이 필요하면 옵저버를 추가하면 됨

class PointObserver(OrderObserver):
    def update(self, order):
        return "포인트 적립"

 

구분 역할
Order 상태 변경과 알림을 담당한다.
OrderObserver 상태 변화를 전달받는 공통 형태를 정의한다.
NotificationObserver, StockObserver, DeliveryObserver 상태 변화 이후의 실제 작업을 수행한다.

 

 

 


 

장점

 

장점

장점 설명
결합도 감소 상태를 변경하는 객체가 후속 작업의 구체 구현을 직접 알 필요가 줄어든다.
확장 용이 새로운 후속 작업을 옵저버로 추가할 수 있다.
책임 분리 상태 변경 로직과 후속 처리 로직을 분리할 수 있다.
재사용성 증가 같은 옵저버를 여러 Subject에 등록해 사용할 수 있다.

 

 

효과적인 상황

상황 이유
하나의 상태 변화가 여러 기능에 영향을 주는 경우 후속 작업을 옵저버로 분리할 수 있다.
후속 작업이 자주 추가되거나 변경되는 경우 새로운 옵저버를 추가하는 방식으로 확장할 수 있다.
상태 변화 발생자와 처리자를 분리하고 싶은 경우 Subject가 Observer의 구체 구현을 몰라도 된다.
이벤트 기반 구조가 필요한 경우 변화가 발생했을 때 관련 작업을 실행하는 흐름을 만들 수 있다.

 

 

 


 

주의점

주의점

주의점 설명
흐름 추적 어려움 어떤 옵저버가 등록되어 있는지 확인해야 전체 동작을 알 수 있다.
실행 순서 문제 옵저버 실행 순서가 중요하다면 명확한 기준이 필요하다.
예외 처리 필요 하나의 옵저버에서 오류가 나면 나머지 옵저버 실행에 영향을 줄 수 있다.
옵저버 증가 관리 등록되는 옵저버가 많아지면 전체 구조를 관리하기 어려워질 수 있다.
메모리 누수 가능성 옵저버 등록 후 해제하지 않으면 불필요한 참조가 남을 수 있다.

 

 

ex) 옵저버 패턴은 상태 변화와 후속 처리를 분리하는 데 유용하지만, 흐름이 지나치게 분산될 수 있음

직접 호출 방식에서는 어떤 작업이 실행되는지 코드에서 바로 확인 가능

def complete_order(order):
    order.status = "paid"

    send_notification(order)
    decrease_stock(order)
    prepare_delivery(order)

 

하지만 옵저버 패턴을 사용하면 후속 작업이 등록된 옵저버들로 분리됨

order.add_observer(NotificationObserver())
order.add_observer(StockObserver())
order.add_observer(DeliveryObserver())

order.complete()

이 구조에서는 order.complete()만 보고는 어떤 후속 작업이 실행되는지 바로 알기 어려울 수 있음

=> 결합도는 낮아지지만, 실행 흐름을 추적하는 비용은 늘어날 수 있음 

 

ex) 알림 발송은 실패해도 주문 완료 자체는 유지되어야 할 수 있고, 재고 차감이 실패하면 주문 완료를 되돌려야 할 수도 있음

=> 후속 작업마다 실패했을 때의 처리 방식이 다를 수 있음.

=> 단순히 알림을 보낸다에서 끝내지 않고, 실행 순서와 실패 처리 방식을 함께 설계

고려사항

질문 의미
상태 변화 이후 실행될 작업들이 독립적인가? 각 옵저버를 분리해도 되는지 확인한다.
옵저버 실행 순서가 중요한가? 순서가 중요하다면 명시적으로 관리해야 한다.
일부 옵저버가 실패하면 어떻게 할 것인가? 예외 처리, 재시도, 보상 처리를 고민한다.
어떤 옵저버가 등록되어 있는지 추적 가능한가? 흐름이 지나치게 숨겨지지 않도록 관리한다.
등록과 해제가 필요한 구조인가? 불필요한 참조나 메모리 누수를 방지한다.