옵저버 패턴
Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 94번째.
옵저버 패턴(Observer pattern)은 객체 간의 일대다 의존성을 정의합니다. 한 객체(주체(subject))의 상태가 변경되면, 그에 의존하는 모든 객체들(옵저버(observers))이 자동으로 알림을 받습니다. 이는 이벤트 시스템, UI 업데이트 또는 여러 객체가 변경 사항에 반응해야 하는 모든 시나리오에 이상적입니다.
이 패턴은 두 가지 핵심 역할을 포함합니다: 옵저버 목록을 유지하고 이들에게 알림을 보내는 Subject와, 업데이트 인터페이스를 구현하는 Observers입니다:
#include <iostream>
#include <vector>
#include <algorithm>
class Observer {
public:
virtual void update(int value) = 0;
virtual ~Observer() = default;
};
class Subject {
std::vector<Observer*> observers;
int state = 0;
public:
void attach(Observer* obs) {
observers.push_back(obs);
}
void detach(Observer* obs) {
observers.erase(
std::remove(observers.begin(), observers.end(), obs),
observers.end());
}
void setState(int value) {
state = value;
notify();
}
void notify() {
for (Observer* obs : observers) {
obs->update(state);
}
}
};
class Display : public Observer {
std::string name;
public:
Display(const std::string& n) : name(n) {}
void update(int value) override {
std::cout << name << " received: " << value << "\n";
}
};
int main() {
Subject sensor;
Display screen1("Screen1"), screen2("Screen2");
sensor.attach(&screen1);
sensor.attach(&screen2);
sensor.setState(42); // 두 디스플레이 모두 알림을 받음
}setState()가 호출되면, 주체(subject)는 등록된 모든 옵저버를 순회하며 그들의 update() 메서드를 호출합니다. 옵저버는 언제든지 연결(attach)하거나 분리(detach)할 수 있어, 시스템을 유연하고 느슨하게 결합된(loosely coupled) 상태로 만듭니다.
한 객체의 변경사항이 다른 객체들을 업데이트해야 하고, 이 객체들이 서로 강하게 결합되지 않기를 원할 때 Observer를 사용하세요.
챌린지
쉬움옵저버(Observer) 패턴을 사용하여 기상 관측소(Weather Station) 모니터링 시스템을 구축해 보겠습니다. 온도 데이터를 추적하고 온도가 변할 때마다 여러 디스플레이 장치에 자동으로 알림을 보내는 기상 관측소를 만들 것입니다. 이는 이 패턴의 전형적인 실제 응용 사례입니다.
코드는 다음 세 개의 파일로 구성됩니다:
Observer.h: 옵저버 인터페이스와 구체적인 디스플레이 옵저버를 정의합니다.옵저버가 알림을 받기 위해 구현하는 순수 가상 메서드
update(double temperature)와 가상 소멸자를 포함한 추상 클래스Observer를 생성하세요.그런 다음
Observer를 상속받는TemperatureDisplay클래스를 생성하세요. 각 디스플레이는 생성자를 통해 설정된 이름(string)을 가집니다.update()가 호출되면 다음과 같이 출력해야 합니다:[DisplayName]: Temperature is [temperature] degreesWeatherStation.h: 온도를 유지하고 옵저버에게 알림을 보내는 주체(Subject)를 생성합니다.WeatherStation클래스는 다음을 수행해야 합니다:- 옵저버 포인터 리스트와 현재 온도(
0.0으로 초기화)를 저장합니다. - 옵저버를 등록하는
attach(Observer* obs)메서드를 가집니다. - 옵저버를 제거하는
detach(Observer* obs)메서드를 가집니다. - 온도를 업데이트하고 등록된 모든 옵저버에게 알림을 보내는
setTemperature(double temp)메서드를 가집니다. - 현재 온도로 각 옵저버의
update()를 호출하는 프라이빗notify()메서드를 가집니다.
- 옵저버 포인터 리스트와 현재 온도(
main.cpp: 옵저버 패턴이 작동하는 모습을 보여줍니다.세 개의 입력을 읽습니다:
- 첫 번째 디스플레이의 이름 (string)
- 두 번째 디스플레이의 이름 (string)
- 온도 값 (double)
WeatherStation하나와 제공된 이름으로 두 개의TemperatureDisplay객체를 생성하세요. 두 디스플레이를 스테이션에 연결(attach)한 다음, 온도를 입력된 값으로 설정하세요. 두 디스플레이 모두 자동으로 업데이트를 받고 메시지를 출력해야 합니다.그 후, 첫 번째 디스플레이를 연결 해제(detach)하고 입력값보다 5도 높은 새로운 온도를 설정하세요. 두 번째 디스플레이만 이 업데이트를 받아야 합니다.
예를 들어, 입력값이 Kitchen, Bedroom, 22.5인 경우:
Kitchen: Temperature is 22.5 degrees
Bedroom: Temperature is 22.5 degrees
Bedroom: Temperature is 27.5 degrees입력값이 Office, Lobby, 18.0인 경우:
Office: Temperature is 18 degrees
Lobby: Temperature is 18 degrees
Lobby: Temperature is 23 degrees옵저버 패턴이 어떻게 느슨하게 결합된(loosely coupled) 시스템을 만드는지 주목하세요. 기상 관측소는 특정 디스플레이에 대해 알 필요가 없으며, 단지 듣고 있는 대상에게 알림을 보낼 뿐입니다. 옵저버를 연결 해제하면 자동으로 업데이트 수신이 중단됩니다.
치트 시트
옵저버 패턴(Observer pattern)은 주체(subject)의 상태가 변경될 때 모든 옵저버(observer)에게 자동으로 알림을 보내는 일대다 의존 관계를 정의합니다.
이 패턴에는 두 가지 핵심 역할이 있습니다:
- Subject: 옵저버 목록을 유지하고 상태 변경 시 이들에게 알림을 보냅니다.
- Observer: 알림을 받기 위한 업데이트 인터페이스를 구현합니다.
기본 구현:
#include <iostream>
#include <vector>
#include <algorithm>
class Observer {
public:
virtual void update(int value) = 0;
virtual ~Observer() = default;
};
class Subject {
std::vector<Observer*> observers;
int state = 0;
public:
void attach(Observer* obs) {
observers.push_back(obs);
}
void detach(Observer* obs) {
observers.erase(
std::remove(observers.begin(), observers.end(), obs),
observers.end());
}
void setState(int value) {
state = value;
notify();
}
void notify() {
for (Observer* obs : observers) {
obs->update(state);
}
}
};
class Display : public Observer {
std::string name;
public:
Display(const std::string& n) : name(n) {}
void update(int value) override {
std::cout << name << " received: " << value << "\n";
}
};주요 메서드:
attach(): 알림을 받을 옵저버를 등록합니다.detach(): 알림 목록에서 옵저버를 제거합니다.notify(): 모든 옵저버를 순회하며update()메서드를 호출합니다.
한 객체의 변경 사항이 다른 객체들에 업데이트되어야 하고, 이들 사이의 결합도를 낮게 유지하고 싶을 때 옵저버 패턴을 사용하세요.
직접 해보기
#include <iostream>
#include <string>
#include "WeatherStation.h"
using namespace std;
int main() {
// 입력 읽기
string display1Name, display2Name;
double temperature;
cin >> display1Name;
cin >> display2Name;
cin >> temperature;
// TODO: WeatherStation 객체 생성
// TODO: 입력받은 이름으로 두 개의 TemperatureDisplay 객체 생성
// TODO: 두 디스플레이를 weather station에 연결(attach)
// TODO: 온도를 입력값으로 설정
// (두 디스플레이 모두 메시지를 출력해야 함)
// TODO: 첫 번째 디스플레이 연결 해제(detach)
// TODO: 온도를 입력값보다 5도 높게 설정
// (두 번째 디스플레이만 출력해야 함)
return 0;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.