매번 아리송한 옵저버 패턴
옵저버 패턴은 어떤 객체의 상태 변화가 있을 때, 그 변화를 구독 중인 다른 객체들에게 자동으로 알림을 전달하여 각자 필요한 동작을 하도록 만드는 설계 패턴이다. GUI, 이벤트 처리, 데이터 동기화 등에서 널리 사용된다.

약속과 반응의 서사시: C++로 구현하는 옵저버 패턴
우리는 흔히 ‘연결’이라는 단어를 긍정적으로만 받아들입니다. 하지만 프로그래밍의 세계에서 무분별한 연결은 곧 ‘의존성’이라는 족쇄가 되기도 하죠. 마치 모든 시청자의 집을 일일이 방문하며 “새 영상이 나왔어요!”라고 외치는 유튜버를 상상해 보세요. 그것은 소통이 아니라 노동이며, 결국 시스템 전체를 무너뜨리는 비효율의 극치가 될 것입니다.
옵저버 패턴은 이 지점에서 ‘약속’이라는 우아한 해법을 제시합니다. 구체적인 누군가를 아는 대신, ‘알림을 받을 수 있는 자격’만을 확인하는 것이죠. 자, 이제 C++라는 언어의 문법을 빌려 이 정교한 약속의 무대를 직접 구현해 볼까요?
1. 인터페이스: 신뢰의 명세서
가장 먼저 필요한 것은 ‘관찰자라면 마땅히 갖춰야 할 소양’을 정의하는 일입니다. 이를 프로그래밍에서는 인터페이스, C++에서는 순수 가상 함수를 포함한 추상 클래스라고 부릅니다.
이것은 마치 방송국과 시청자 사이의 표준 수신기 규격과 같습니다. 방송국은 시청자가 누구인지 몰라도, 그가 ‘표준 수신기’만 가지고 있다면 신호를 보낼 수 있죠.
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
// 1. 인터페이스 (Observer Interface)
// "구독자라면 무조건 update 기능이 있어야 해!"라고 약속하는 명세서입니다.
class IObserver {
public:
virtual ~IObserver() {} // 가상 소멸자는 객체 지향의 예의입니다.
virtual void update(const string& title) = 0; // "업데이트하라"는 엄격한 명령이죠.
};
2. 구체적인 존재들: 반응하는 객체
약속이 정의되었다면, 이제 그 약속을 이행하는 실제 주체들이 등장할 차례입니다. 철수와 영희는 각자의 이름을 가진 ‘구독자’가 되어, 유튜버의 신호에 반응할 준비를 마칩니다.
// 2. 실제 구독자 (Concrete Observer)
class Subscriber : public IObserver {
private:
string name;
public:
Subscriber(string n) : name(n) {}
// 약속된 동작을 실제로 수행합니다.
void update(const string& title) override {
cout << "[" << name << "]님! 알고리즘이 당신을 불렀어요. 새 영상: " << title << endl;
}
};
3. 유튜버: 권력을 가진 주체, 그러나 고독한 발신자
이제 이 드라마의 주인공인 Youtuber 클래스를 살펴봅시다. 이 클래스는 ‘누가 나를 지켜보고 있는가’를 관리하는 명부(Subscriber List)를 가집니다. 하지만 유튜버는 구독자의 사생활에 개입하지 않습니다. 오직 영상이 올라가는 순간, 명부에 적힌 주소로 신호탄을 쏠 뿐입니다.

// 3. 유튜버 (Subject)
class Youtuber {
private:
vector<IObserver*> subscribers;
public:
void subscribe(IObserver* observer) {
subscribers.push_back(observer);
cout << ">> 새로운 관찰자가 망원경을 설치했습니다." << endl;
}
void unsubscribe(IObserver* observer) {
subscribers.erase(
remove(subscribers.begin(), subscribers.end(), observer),
subscribers.end()
);
cout << ">> 한 명의 관찰자가 시선을 거두었습니다." << endl;
}
// 알림 보내기 (Notify)
void notifySubscribers(const string& title) {
for (IObserver* sub : subscribers) {
sub->update(title);
}
}
// [핵심] 영상 업로드: 단 하나의 사건이 거대한 연쇄 반응을 일으킵니다.
void uploadVideo(const string& title) {
cout << "\n🎥 [이벤트 발생] 유튜버가 '" << title << "' 영상을 업로드했습니다!" << endl;
// 내부적으로 notifySubscribers를 호출하여 모든 구독자에게 자동으로 전파합니다.
notifySubscribers(title);
}
};
4. 분석: 단 하나의 동작, 수만 개의 메아리
위 코드의 백미는 바로 uploadVideo 메서드입니다. 유튜버는 그저 "영상을 올린다"는 자신의 본분에만 충실했을 뿐입니다. 하지만 그 이면에서는 notifySubscribers가 조용히 호출되며, 명부에 등록된 수많은 구독자에게 자동으로 소식이 전달되죠.
여기서 주체(Subject)는 관찰자(Observer)가 정확히 누구인지, 현재 몇 명인지, 심지어 그들이 알림을 받고 어떤 기괴한 춤을 추는지조차 알 필요가 없습니다. 그저 "변경이 발생했다"는 소리를 지를 뿐이고, 그 외침에 반응하는 것은 오롯이 관찰자들의 몫입니다. 이것이 바로 기술적 합리성이 추구하는 **‘느슨한 결합(Loose Coupling)’**의 본질입니다.
5. 확장: 변화에 강한 구조를 향하여

옵저버 패턴은 단순히 유튜버와 구독자의 관계에 머물지 않습니다. 여러분이 매일 사용하는 스마트폰의 UI가 데이터 변화에 맞춰 실시간으로 갱신될 때, 혹은 주식 차트가 눈 깜빡임보다 빠르게 요동칠 때, 그 기저에는 이 보이지 않는 '약속의 연결고리'가 작동하고 있습니다.
- 이벤트 처리: 마우스 클릭 한 번이 수만 개의 함수를 깨웁니다.
- 상태 동기화: 서버의 데이터가 변하면 모든 클라이언트의 화면이 일제히 정렬됩니다.
과연 우리의 설계는 이토록 유연한가요? 아니면 작은 변화 하나에도 온 몸을 비틀며 고통스러워하는 '강결합'의 늪에 빠져 있지는 않나요?
관계의 거리두기가 주는 자유
결국 중요한 것은 ‘무지의 지(知)’입니다. 주체가 관찰자를 모를수록, 역설적으로 시스템은 더 큰 자유를 얻습니다. 새로운 구독자가 수천 명 늘어나도 유튜버의 uploadVideo 코드는 단 한 줄도 수정될 필요가 없기 때문이죠.
낙관과 경계 사이에서, 옵저버 패턴은 우리에게 '적절한 거리'가 주는 효율성을 가르쳐줍니다. 변화에 강한 구조를 만든다는 것은, 결국 서로를 구속하지 않으면서도 필요한 순간에 일사불란하게 움직이는 군무를 설계하는 일일지도 모릅니다. 여러분의 다음 프로젝트에서도 이 우아한 메아리가 울려 퍼질 수 있을지는, 이제 여러분의 설계에 달려 있습니다.