Observer 패턴
상태 변화를 구독자들에게 자동으로 알려주는 Observer 패턴을 알아봅니다.
#유튜브를 구독하면 벌어지는 일
좋아하는 유튜브 채널이 있다고 해보자.
새 영상이 올라왔는지 확인하려고 매시간 채널에 들어가서 확인한다면?

미친 짓이다. 그래서 우리는 구독 버튼을 누른다.
구독을 하면 새 영상이 올라올 때 알림이 자동으로 온다. 내가 확인하러 갈 필요가 없다. 채널(발행자)이 나(구독자)에게 알려주는 것이다.
이게 바로 Observer 패턴이다. 그리고 프론트엔드 개발에서 가장 중요한 패턴 중 하나라고 감히 말할 수 있다.
visible: false
Observer란?
객체의 상태가 변할 때, 그 객체를 구독하고 있는 모든 객체에게 자동으로 알려주는 패턴
핵심 구조는 단순하다.
- Subject (발행자) — 상태를 가지고 있고, 구독자 목록을 관리한다
- Observer (구독자) — Subject의 상태 변화를 통보받는다
Subject의 상태가 바뀌면, 등록된 모든 Observer에게 "야, 바뀌었어!" 하고 알려준다.
visible: false
코드로 보기
기본 Event Emitter
가장 직관적인 형태부터 만들어보자.
// Observer 인터페이스
interface Observer<T> {
update(data: T): void;
}
// Subject 인터페이스
interface Subject<T> {
subscribe(observer: Observer<T>): void;
unsubscribe(observer: Observer<T>): void;
notify(data: T): void;
}
// 이벤트 발행자
class EventEmitter<T> implements Subject<T> {
private observers: Set<Observer<T>> = new Set();
subscribe(observer: Observer<T>): void {
this.observers.add(observer);
console.log(`구독자 추가! 현재 ${this.observers.size}명`);
}
unsubscribe(observer: Observer<T>): void {
this.observers.delete(observer);
console.log(`구독 해제. 현재 ${this.observers.size}명`);
}
notify(data: T): void {
this.observers.forEach((observer) => observer.update(data));
}
}유튜브 채널 시뮬레이션
interface Video {
title: string;
duration: number;
}
// 유튜브 채널 (Subject)
class YouTubeChannel extends EventEmitter<Video> {
private channelName: string;
constructor(name: string) {
super();
this.channelName = name;
}
uploadVideo(title: string, duration: number): void {
const video: Video = { title, duration };
console.log(`\n📹 [${this.channelName}] 새 영상 업로드: "${title}"`);
this.notify(video); // 모든 구독자에게 알림!
}
}
// 구독자 (Observer)
class Subscriber implements Observer<Video> {
constructor(private name: string) {}
update(video: Video): void {
console.log(
`🔔 ${this.name}: "${video.title}" 알림 도착! (${video.duration}분)`,
);
}
}
// 사용
const channel = new YouTubeChannel("코딩하는 준범");
const sub1 = new Subscriber("김철수");
const sub2 = new Subscriber("이영희");
const sub3 = new Subscriber("박민수");
channel.subscribe(sub1);
channel.subscribe(sub2);
channel.subscribe(sub3);
channel.uploadVideo("Observer 패턴 10분 정리", 10);
// 📹 [코딩하는 준범] 새 영상 업로드: "Observer 패턴 10분 정리"
// 🔔 김철수: "Observer 패턴 10분 정리" 알림 도착! (10분)
// 🔔 이영희: "Observer 패턴 10분 정리" 알림 도착! (10분)
// 🔔 박민수: "Observer 패턴 10분 정리" 알림 도착! (10분)
channel.unsubscribe(sub2); // 이영희 구독 해제
channel.uploadVideo("Decorator 패턴 실전편", 15);
// 📹 [코딩하는 준범] 새 영상 업로드: "Decorator 패턴 실전편"
// 🔔 김철수: "Decorator 패턴 실전편" 알림 도착! (15분)
// 🔔 박민수: "Decorator 패턴 실전편" 알림 도착! (15분)구독자가 늘어도 줄어도 채널(Subject)의 코드는 변하지 않는다.

visible: false
더 실용적인 예시: 상태 관리 Store
프론트엔드에서 가장 많이 쓰는 형태는 Store다. Zustand, Redux의 핵심이 이것이다.
type Listener<T> = (state: T) => void;
class Store<T> {
private listeners: Set<Listener<T>> = new Set();
private state: T;
constructor(initialState: T) {
this.state = initialState;
}
getState(): T {
return this.state;
}
setState(updater: (prev: T) => T): void {
this.state = updater(this.state);
this.emit(); // 상태가 바뀌면 모든 리스너에게 알림
}
subscribe(listener: Listener<T>): () => void {
this.listeners.add(listener);
// 구독 해제 함수를 반환 — 이 패턴이 정말 중요하다
return () => this.listeners.delete(listener);
}
private emit(): void {
this.listeners.forEach((listener) => listener(this.state));
}
}
// 사용
interface AppState {
count: number;
user: string | null;
}
const store = new Store<AppState>({ count: 0, user: null });
// 구독
const unsubscribe = store.subscribe((state) => {
console.log(`카운트가 변경됨: ${state.count}`);
});
store.setState((prev) => ({ ...prev, count: prev.count + 1 }));
// 카운트가 변경됨: 1
store.setState((prev) => ({ ...prev, count: prev.count + 1 }));
// 카운트가 변경됨: 2
unsubscribe(); // 더 이상 알림 받지 않음
store.setState((prev) => ({ ...prev, count: prev.count + 1 }));
// (아무 출력 없음)subscribe가 구독 해제 함수를 반환하는 패턴을 주목하자. React의 useEffect 클린업, Zustand의 subscribe, RxJS의 unsubscribe가 모두 이 패턴을 따른다.
visible: false
사실 이미 매일 쓰고 있다
Observer 패턴이 낯설게 느껴질 수도 있지만, 프론트엔드 개발자라면 이미 매일 사용하고 있다.
1. addEventListener
// DOM 이벤트 = Observer 패턴 그 자체
const button = document.querySelector("#myButton");
// subscribe
button.addEventListener("click", (e) => {
console.log("클릭됨!");
});
// 여러 Observer 등록 가능
button.addEventListener("click", logAnalytics);
button.addEventListener("click", playClickSound);
button.addEventListener("click", updateUI);addEventListener는 Observer 패턴의 가장 원초적인 형태다. 버튼(Subject)을 클릭하면 등록된 모든 핸들러(Observer)가 호출된다.
2. React의 useState
function Counter() {
const [count, setCount] = useState(0);
// ^^^^^^^^ 상태가 바뀌면 → 컴포넌트가 리렌더링(=알림)
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}setCount로 상태를 변경하면 React가 해당 컴포넌트를 자동으로 리렌더링한다. 이것이 바로 Observer 패턴이다. 상태(Subject)가 바뀌면 컴포넌트(Observer)가 반응하는 것이다.
3. RxJS Observable
import { fromEvent, debounceTime, map } from "rxjs";
// 검색창 입력을 구독
const search$ = fromEvent(searchInput, "input").pipe(
debounceTime(300),
map((e: Event) => (e.target as HTMLInputElement).value),
);
// Observer 등록
search$.subscribe((query) => {
console.log(`검색어: ${query}`);
fetchSearchResults(query);
});RxJS는 Observer 패턴을 스테로이드 맞은 버전으로 확장한 것이다. 이벤트 스트림을 구독하고, 변환하고, 조합할 수 있다.
4. WebSocket
const ws = new WebSocket("wss://chat.example.com");
// 서버가 메시지를 보내면 → 자동으로 알림
ws.addEventListener("message", (event) => {
const data = JSON.parse(event.data);
appendMessage(data);
});
ws.addEventListener("open", () => console.log("연결됨"));
ws.addEventListener("close", () => console.log("연결 끊김"));WebSocket도 마찬가지다. 서버(Subject)가 메시지를 보내면 클라이언트의 핸들러(Observer)가 호출된다.

visible: false
언제 사용하면 좋을까?
-
한 객체의 변화가 다른 여러 객체에 영향을 줄 때
- 상태 관리, 실시간 데이터, 이벤트 시스템
-
구독자를 런타임에 동적으로 추가/제거해야 할 때
- 컴포넌트 마운트/언마운트 시 구독 관리
-
발행자가 구독자의 구체적인 타입을 알 필요 없을 때
- 느슨한 결합이 필요한 모든 상황
visible: false
장단점
| 장점 | 단점 |
|---|---|
| Subject와 Observer 간 느슨한 결합 | 구독 해제를 빠뜨리면 메모리 누수 발생 |
| 런타임에 구독 관계를 동적으로 변경 가능 | Observer가 많으면 알림 순서 예측이 어려움 |
| OCP — 기존 코드 수정 없이 새 Observer 추가 | 순환 의존성이 생기면 무한 루프 위험 |
| 이벤트 기반 아키텍처의 핵심 | 디버깅 시 이벤트 흐름 추적이 까다로움 |
visible: false
정리하며
Observer는 상태 변화를 구독자에게 자동으로 알려주는 패턴이다.
핵심을 한 줄로 요약하면,
"변하면 알려줘. 내가 매번 확인하러 가지 않을게."
addEventListener, useState, subscribe, on — 프론트엔드에서 매일 만나는 이 API들이 전부 Observer 패턴 위에 세워져 있다. 이 패턴을 이해하면 프론트엔드의 반응형 프로그래밍이 왜 이렇게 동작하는지 근본적으로 이해할 수 있다.
과장 좀 보태서, 프론트엔드 개발자에게 Observer는 공기 같은 패턴이다. 평소에는 의식하지 못하지만, 없으면 아무것도 할 수 없다.
visible: false
#Reference
Refactoring Guru - Observer
refactoring.guru