한 객체의 상태가 바뀌면 그에 의존하는 여러 객체가 갱신돼야 하는 경우가 있다. 주가가 바뀌면 차트, 호가창, 알림이 함께 갱신된다. 주체가 이 의존자들을 하나씩 직접 호출하면, 새 의존자가 생길 때마다 주체를 고쳐야 하고 둘은 강하게 결합된다. Observer 는 주체가 구독자 목록에만 통지하게 해 이 결합을 끊는다.
공통 의도
Observer 의 의도는 세 가지다.
- 일대다 통지 — Subject 하나가 여러 Observer 에게 상태 변화를 알린다.
- 느슨한 결합 — Subject 는 Observer 의 구체 타입을 모른다. 공통 인터페이스로만 통지하므로, 누가 듣는지 몰라도 된다.
- 동적 구독 — Observer 는 런타임에 구독하고 해지한다. 관계가 고정되지 않는다.
GoF 원형
GoF 는 Subject 와 Observer 로 정의한다. Subject 는 Observer 목록을 들고 attach, detach, notify 를 제공한다. 상태가 바뀌면 notify 가 목록을 돌며 각 Observer 의 갱신 메서드를 호출한다.
sequenceDiagram
participant S as Subject
participant A as Observer A
participant B as Observer B
Note over S: 상태 변경
S->>A: update()
S->>B: update()
interface Observer {
void update(int price);
}
class Stock {
private final List<Observer> observers = new ArrayList<>();
void attach(Observer o) { observers.add(o); }
void setPrice(int price) {
for (Observer o : observers) o.update(price);
}
}
Stock 은 자신을 구독한 Observer 가 차트인지 알림인지 모른다. setPrice 가 목록을 돌며 통지할 뿐이다. 새 구독자는 attach 로 들어오고 detach 로 빠진다.
언어별 구현
Java 의 내장 java.util.Observer 와 Observable 은 Java 9 부터 deprecated 다. 스레드 안전성과 설계 한계 때문에, 지금은 직접 구현하거나 PropertyChangeListener, 프레임워크의 이벤트 리스너를 쓴다.
Python 은 콜백 목록으로 간단히 표현한다. Subject 가 함수 리스트를 들고, 변화가 생기면 각 콜백을 호출한다.
class Stock:
def __init__(self):
self._observers = []
def subscribe(self, callback):
self._observers.append(callback)
def set_price(self, price):
for cb in self._observers:
cb(price)
TypeScript 는 Node 의 EventEmitter 가 대표적이다. emitter.on("price", handler) 로 구독하고 emitter.emit("price", value) 로 통지한다. 더 나아가 RxJS 의 Observable 은 Observer 를 스트림으로 확장한다.
pub/sub 와의 구분
Observer 와 pub/sub 는 닮았지만 매개 여부가 다르다.
- Observer — Subject 가 Observer 목록을 직접 들고 통지한다. 둘이 서로를 안다.
- pub/sub — 발행자와 구독자가 이벤트 채널이나 브로커를 매개로 통신한다. 서로를 직접 모른다.
통지 방식에도 두 갈래가 있다. notify 가 바뀐 데이터를 함께 밀어주는 push, Observer 가 통지를 받고 Subject 에 되물어 필요한 값을 가져오는 pull 이다. push 는 간결하고, pull 은 Observer 가 필요한 만큼만 가져간다.
리액티브로의 확장
Observer 는 현대 리액티브 프로그래밍의 뿌리다. RxJS, Reactor 같은 리액티브 스트림은 Observer 에 연산자 합성과 배압(backpressure) 제어를 더한 형태다. 이벤트 기반 아키텍처에서 서비스가 이벤트를 발행하고 다른 서비스가 구독하는 흐름도 같은 의도의 확장이다. 하나의 상태 변화가 여러 곳으로 전파되는 문제를, 규모만 키워 다시 푼다.
결론
Observer 는 상태 변화를 구독자에게 통지하는 일대다 관계로 결합을 끊는다. Subject 는 누가 듣는지 모른 채 통지만 한다. 판단은 한 가지 질문으로 좁혀진다.
- 한 객체의 변화가 여러 객체로 전파돼야 하고, 그 의존자 목록이 고정되지 않는가. 그렇다면 직접 호출보다 Observer.
EventEmitter, RxJS, 이벤트 기반 아키텍처가 모두 같은 의도의 변주다. 브로커를 매개하는 pub/sub 와는 서로를 직접 아는지로 갈린다.