Menu
Coddy logo textTech

옵저버 패턴

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를 사용하세요.

challenge icon

챌린지

쉬움

옵저버(Observer) 패턴을 사용하여 기상 관측소(Weather Station) 모니터링 시스템을 구축해 보겠습니다. 온도 데이터를 추적하고 온도가 변할 때마다 여러 디스플레이 장치에 자동으로 알림을 보내는 기상 관측소를 만들 것입니다. 이는 이 패턴의 전형적인 실제 응용 사례입니다.

코드는 다음 세 개의 파일로 구성됩니다:

  • Observer.h: 옵저버 인터페이스와 구체적인 디스플레이 옵저버를 정의합니다.

    옵저버가 알림을 받기 위해 구현하는 순수 가상 메서드 update(double temperature)와 가상 소멸자를 포함한 추상 클래스 Observer를 생성하세요.

    그런 다음 Observer를 상속받는 TemperatureDisplay 클래스를 생성하세요. 각 디스플레이는 생성자를 통해 설정된 이름(string)을 가집니다. update()가 호출되면 다음과 같이 출력해야 합니다:

    [DisplayName]: Temperature is [temperature] degrees
  • WeatherStation.h: 온도를 유지하고 옵저버에게 알림을 보내는 주체(Subject)를 생성합니다.

    WeatherStation 클래스는 다음을 수행해야 합니다:

    • 옵저버 포인터 리스트와 현재 온도(0.0으로 초기화)를 저장합니다.
    • 옵저버를 등록하는 attach(Observer* obs) 메서드를 가집니다.
    • 옵저버를 제거하는 detach(Observer* obs) 메서드를 가집니다.
    • 온도를 업데이트하고 등록된 모든 옵저버에게 알림을 보내는 setTemperature(double temp) 메서드를 가집니다.
    • 현재 온도로 각 옵저버의 update()를 호출하는 프라이빗 notify() 메서드를 가집니다.
  • main.cpp: 옵저버 패턴이 작동하는 모습을 보여줍니다.

    세 개의 입력을 읽습니다:

    1. 첫 번째 디스플레이의 이름 (string)
    2. 두 번째 디스플레이의 이름 (string)
    3. 온도 값 (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;
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨