본문 바로가기
-----ETC-----/C++ 고급 프로그래밍과 응용 프로젝트 시리즈

[C++ 고급 프로그래밍과 응용 프로젝트 시리즈] Day 11: 디자인 패턴 심화 - 옵저버 패턴 (Observer Pattern)

by cogito21_cpp 2024. 8. 1.
반응형

옵저버 패턴 (Observer Pattern)

옵저버 패턴은 행위 패턴 중 하나로, 객체 간의 일대다 관계를 정의합니다. 한 객체의 상태 변화가 다른 객체들에 자동으로 통지될 수 있도록 합니다. 이를 통해 객체들 간의 느슨한 결합을 유지하면서도 이벤트 기반 프로그래밍을 할 수 있습니다.

 

옵저버 패턴의 특징

  1. 일대다 관계: 하나의 주체(Subject) 객체가 다수의 옵저버(Observer) 객체에 상태 변화를 통지합니다.
  2. 느슨한 결합: 주체와 옵저버는 서로 독립적으로 동작하며, 주체는 옵저버가 누구인지 알 필요가 없습니다.
  3. 이벤트 기반: 상태 변화가 발생할 때 자동으로 통지가 이루어집니다.

옵저버 패턴의 구조

  1. Subject (주체): 상태를 가지고 있으며, 상태 변화 시 옵저버에게 통지합니다.
  2. Observer (옵저버): 주체의 상태 변화를 관찰하고, 변경 내용을 반영합니다.
  3. ConcreteSubject (구체적 주체): 주체의 상태를 구현하고, 옵저버를 관리합니다.
  4. ConcreteObserver (구체적 옵저버): 옵저버 인터페이스를 구현하며, 주체의 상태 변화에 대응합니다.

옵저버 패턴 UML 다이어그램

+------------+     +----------------+
|   Subject  |<--->|    Observer    |
+------------+     +----------------+
| -state     |     | +update()      |
| +attach()  |     +----------------+
| +detach()  |           /\
| +notify()  |           ||
+------------+           ||
      /\                +----------------+
      ||                | ConcreteObserver|
+----------------+      +----------------+
| ConcreteSubject|
+----------------+

옵저버 패턴 구현 예제

다음 예제는 옵저버 패턴을 사용하여 주식 가격 변동을 통지하는 프로그램을 구현합니다.

 

Observer 인터페이스 정의

#include <iostream>
#include <vector>
#include <memory>

// 옵저버 인터페이스
class Observer {
public:
    virtual void update(float price) = 0;
    virtual ~Observer() = default;
};

 

Subject 클래스 정의

// 주체 클래스
class Subject {
private:
    std::vector<Observer*> observers;
    float price;

public:
    void attach(Observer* observer) {
        observers.push_back(observer);
    }

    void detach(Observer* observer) {
        observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
    }

    void notify() {
        for (Observer* observer : observers) {
            observer->update(price);
        }
    }

    void setPrice(float newPrice) {
        price = newPrice;
        notify();
    }
};

 

ConcreteObserver 클래스 정의

// 구체적 옵저버 클래스
class ConcreteObserver : public Observer {
private:
    std::string name;
    Subject& subject;

public:
    ConcreteObserver(const std::string& name, Subject& subject) : name(name), subject(subject) {
        subject.attach(this);
    }

    ~ConcreteObserver() {
        subject.detach(this);
    }

    void update(float price) override {
        std::cout << "Observer " << name << ": Price updated to " << price << std::endl;
    }
};

 

옵저버 패턴 사용 예제

int main() {
    Subject stock;

    ConcreteObserver observer1("Observer1", stock);
    ConcreteObserver observer2("Observer2", stock);

    stock.setPrice(100.0f);
    stock.setPrice(105.5f);

    return 0;
}

 

이 예제에서 Subject 클래스는 주식 가격을 관리하고, 가격이 변경될 때 모든 옵저버에게 통지합니다. ConcreteObserver 클래스는 옵저버 인터페이스를 구현하며, 주체의 상태 변화에 따라 업데이트를 수행합니다.


옵저버 패턴의 응용

옵저버 패턴은 다양한 응용 분야에서 사용할 수 있습니다. 몇 가지 예를 들어보겠습니다.

  1. GUI 이벤트 시스템: 버튼 클릭, 마우스 움직임 등 GUI 이벤트를 처리하는 시스템에서 사용됩니다.
  2. 모델-뷰-컨트롤러(MVC) 아키텍처: 모델의 상태 변화가 뷰에 반영되도록 하는 데 사용됩니다.
  3. 이벤트 기반 프로그래밍: 다양한 이벤트를 처리하는 시스템에서 사용됩니다.

실습 문제

문제 1: 옵저버 패턴을 사용하여 날씨 정보를 통지

옵저버 패턴을 사용하여 날씨 정보를 통지하는 프로그램을 작성하세요. 주체는 온도와 습도를 관리하고, 변경 시 옵저버에게 통지합니다.

 

해설:

Observer 인터페이스 정의

#include <iostream>
#include <vector>
#include <algorithm>

// 옵저버 인터페이스
class Observer {
public:
    virtual void update(float temperature, float humidity) = 0;
    virtual ~Observer() = default;
};

 

Subject 클래스 정의

// 주체 클래스
class WeatherStation {
private:
    std::vector<Observer*> observers;
    float temperature;
    float humidity;

public:
    void attach(Observer* observer) {
        observers.push_back(observer);
    }

    void detach(Observer* observer) {
        observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
    }

    void notify() {
        for (Observer* observer : observers) {
            observer->update(temperature, humidity);
        }
    }

    void setWeather(float newTemperature, float newHumidity) {
        temperature = newTemperature;
        humidity = newHumidity;
        notify();
    }
};

 

ConcreteObserver 클래스 정의

// 구체적 옵저버 클래스
class WeatherDisplay : public Observer {
private:
    std::string name;
    WeatherStation& weatherStation;

public:
    WeatherDisplay(const std::string& name, WeatherStation& weatherStation)
        : name(name), weatherStation(weatherStation) {
        weatherStation.attach(this);
    }

    ~WeatherDisplay() {
        weatherStation.detach(this);
    }

    void update(float temperature, float humidity) override {
        std::cout << "Weather Display " << name << ": Temperature = " << temperature
                  << ", Humidity = " << humidity << std::endl;
    }
};

 

옵저버 패턴 사용 예제

int main() {
    WeatherStation station;

    WeatherDisplay display1("Display1", station);
    WeatherDisplay display2("Display2", station);

    station.setWeather(25.0f, 60.0f);
    station.setWeather(22.5f, 55.0f);

    return 0;
}

 

이 예제에서 WeatherStation 클래스는 온도와 습도를 관리하고, 상태가 변경될 때 모든 옵저버에게 통지합니다. WeatherDisplay 클래스는 옵저버 인터페이스를 구현하며, 주체의 상태 변화에 따라 업데이트를 수행합니다.

 

이제 11일차의 학습을 마쳤습니다. 옵저버 패턴의 다양한 구현 방법과 활용 사례에 대해 상세히 학습하고 실습 문제를 풀어보았습니다.

질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "데코레이터 패턴"에 대해 학습하겠습니다.

반응형