변화하는 부분을 포착하기
기존 방식대로라면 코드 구현은 아래와 같다
void measurementsChanged() {
double temp = getTemperature();
double humidity = getHumidity();
double pressure = getPressure();
// 값이 변하면 일일이 전부 알림
currentConditionDisplay.update(temp, humidity, pressure);
statisticsDisplay.update(temp, humidity, pressure);
forecaseDisplay.update(temp, humidity, pressure);
}
위 코드의 문제점은 구체적인 구현에 맞춰 코딩(하드코딩)했으므로 프로그램을 고치지 않고서는 다른 디스플레이 항목을 추가하거나 제거할 수 없다.
그리고 디스플레이 항목과 데이터를 주고 받는데 공통된 인터페이스를 사용하고 있다.
따라서 위 부분을 캡슐화를 진행해야한다 -> 옵저버 패턴 사용

느슨한 결함(Loose Coupling)
객체들이 상호작용할 수 있지만, 서로를 잘 모르는 관계를 의미 한다. 느슨한 결합을 활용하면 유연성이 아주 좋아진다.
옵저버 패턴에서 느슨한 결합을 만드는 방식
- 주제는 옵저버가 특정 인터페이스를 구현한한다는 사실만 안다.
- 옵저버는 언제든지 새로 추가할 수 있다.
- 새로운 형식의 옵저버를 추가할 때도 주제를 변경할 필요가 전혀 없다.
- 주제와 옵저버는 서로 독립적으로 재사용할 수 있다.
- 주제나 옵저버가 달라져도 서로에게 영향을 미치지는 않는다.
디자인 원칙
- 바뀌는 부분은 캡슐화한다.
- 상속보다는 구성을 활용한다.
- 구현보다는 인터페이스에 맞춰서 프로그래밍한다.
- 상호작용하는 개체 사이에서는 가능하면 느슨한 결합을 사용해야 한다.
옵저버 패턴
한 객체(Subject)의 상태가 바뀌면 그 객체에 의존하는 다른 객체(Observer)에게 연락이 가고 자동으로 내용을 갱신되는 방식으로 일대다 의존성을 정의합니다.

C++로 알아보는 간단한 예시
// Observer 인터페이스
class Observer {
public:
virtual void update(const string& news) = 0;
virtual ~Observer() = default;
};
// Subject 인터페이스
class Subject {
public:
virtual void addObserver(shared_ptr<Observer> observer) = 0;
virtual void removeObserver(shared_ptr<Observer> observer) = 0;
virtual void notifyObservers() = 0;
virtual ~Subject() = default;
};
// 구체적인 Subject
class NewsAgency : public Subject {
private:
vector<weak_ptr<Observer>> observers;
string latestNews;
public:
void addObserver(shared_ptr<Observer> observer) override {
observers.push_back(observer);
}
void removeObserver(shared_ptr<Observer> observer) override {
observers.erase(
remove_if(observers.begin(), observers.end(),
[&](const weak_ptr<Observer>& weakObserver) {
auto sharedObserver = weakObserver.lock();
return !sharedObserver || sharedObserver == observer;
}
),
observers.end()
);
}
void notifyObservers() override {
for (auto it = observers.begin(); it != observers.end() ;) {
auto observer = it->lock();
if (observer) {
observer->update(latestNews);
++it;
} else {
it = observers.erase(it);
}
}
}
// news를 발행할 때 구독중인 observer들에게 전부 알림
void publishNews(const string& news) {
latestNews = news;
notifyObservers();
}
};
weak_ptr은 객체에 대한 약한 참조를 가진다. 따라서 이것이 사라져도 객체의 생명주기와는 관련이 없다. 즉 weak_ptr은 객체를 소유하지 않는다. 그러므로 remove 나 notify 시점에 실제 객체가 살아있는지 확인을 해야한다.
lock 함수는 weak_ptr이 가르키던 객체가 아직 살아있다면 해당 객체를 가르키는 shared_ptr을 반환한다. 이미 객체가 사라졌다면 빈 shared_ptr을 반환한다.
erase 함수는 지정한 원소를 지운후 다음 위치를 가르키는 iterator를 반환한다.
class EmailSubscriber : public Observer {
private:
string email;
public:
EmailSubscriber(const string& email) : email(email) {}
void update(const string& news) override {
cout << "[Email] " << email << "에게 뉴스 전송: " << news << '\n';
}
};
class AppSubscriber : public Observer {
private:
string username;
public:
AppSubscriber(const string& username) : username(username) {}
void update(const string& news) override {
cout << "[App] " << username << "에게 푸시 알림: " << news << '\n';
}
};
각 구현체 별로 변화하는 update 함수만 따로 구현해주면 다른 구현체 클래스들도 손쉽게 추가할 수 있다. 이후에 addObserver와 removeObserver을 통해 유기적으로 등록 및 삭제를 할 수 있다.
int main (void) {
NewsAgency agency;
shared_ptr<Observer> emailUser = make_shared<EmailSubscriber>("leokang123@naver.com");
shared_ptr<Observer> appUser = make_shared<AppSubscriber>("leokang123");
agency.addObserver(emailUser);
agency.addObserver(appUser);
agency.publishNews("첫 번째 뉴스입니다.");
cout << "---- 앱 구독자 해지 ----" << '\n';
agency.removeObserver(appUser);
agency.publishNews("두 번째 뉴스입니다.");
return 0;
}
-------- 실행 결과 --------
[Email] leokang123@naver.com에게 뉴스 전송: 첫 번째 뉴스입니다.
[App] leokang123에게 푸시 알림: 첫 번째 뉴스입니다.
---- 앱 구독자 해지 ----
[Email] leokang123@naver.com에게 뉴스 전송: 두 번째 뉴스입니다.
번외
현재 방식은 subject 객체에서 observer들에게 push 방식으로 notify하는 방식이다.
이 방식을 pull 방식으로 구현할수도 있다.
subject에서 바뀐점이 있으면 구독중인 observer들에게 알림을 보내고, observer들이 직접 update함수를 호출하여 값을 갱신하는 방법이다.