KimMK

관찰자(Observer) 패턴 본문

C++/디자인 패턴

관찰자(Observer) 패턴

KimMK 2023. 3. 14. 23:10

디자인패턴 중 행위 패턴인 옵저버 패턴은 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들에 알림이 가고 자동으로 내용이 갱신되는 방법으로 일대다의 의존성을 가진다. 상호작용하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인 패턴이다.

 

옵저버 패턴에는 Subject(주체)Observer(관찰자)라는 두 가지 개념이 사용되는데, Subject는 상태가 변할 수 있는 객체를 나타내고, Observer는 Subject의 상태에 관심이 있는 객체를 나타낸다.

Subject는 Observer의 리스트를 관리하며 Subject의 상태가 변하면 Observer들에게 알림을 보내어 각 Observer가 적절한 조치를 취할 수 있도록 한다.

 

옵저버 패턴은 MVC(Model-View-Controller) 아키텍처 패턴에서 주로 사용되며, GUI 프로그래밍이나 이벤트 기반 시스템에서 자주 활용된다. 예를 들면, 사용자 인터페이스에서 버튼이 클릭되었을 때, 클릭 이벤트를 처리하기 위해 옵저버 패턴을 사용할 수 있다.

게임에서 옵저버 패턴을 사용한다면, 업적 관리에 사용할 수 있다.

각 업적을 이벤트로 따로따로 관리한다면 관리가 너무 어려워진다. 만약, 몬스터 100마리를 죽이는 업적이라면 몬스터에 카운팅 변수를 넣고, 맵 개척 업적이라면 맵을 이동할 때마다 그것을 관리하는 쪽에 넣어줘야 한다. 이것이 너무 많아지면 관리하기가 까다롭다. 따라서 이것을 분산시키는 것이 아니라 옵저버 패턴을 이용해 통합해서 관리를 하는 것이다.

 

장점

  1. 느슨한 결합(Loose Coupling): 주체와 옵저버는 서로 독립적으로 변경될 수 있기 때문에, 즉 객체간의 결합도가 느슨하면서 시스템의 유연성과 확장성을 높이고 일관성 유지가 가능하다.
  2. 이벤트 기반 시스템 구현 용이성: 이벤트 발생시 옵저버들을 통해 이벤트 처리를 하기 때문에 이벤트 기반 시스템에서 사용하기 용이하다.
  3. 상태 변경 시 자동으로 알림 전달: 주체의 상태가 변경될 때마다 옵저버에게 자동으로 알림을 보내기 때문에, 주체와 옵저버간의 일관성을 유지하면서 상태 변경을 처리할 수 있다.
  4. 확장성: 주체와 옵저버 인터페이스를 구현하기만 하면 새로운 옵저버나 주체를 추가하기 용이하다.

 

단점

  1. 순서 보장: 옵저버들은 순서를 보장하지 않기 때문에 옵저버간의 순서에 따라 결과가 달라질 수 있다.
  2. 오버헤드: 옵저버가 많은 경우 주체의 상태 변경시마다 모든 옵저버에게 알림을 보내야 하기 때문에 오버헤드가 발생할 수 있다. 
  3. 데이터 무결성 보장: 주체와 옵저버 사이의 데이터 무결성 보장이 필요한 경우, 옵저버들간의 상호작용을 관리하기 어려울 수 있다.

 

옵저버 패턴을 구현하기 위해서는 Subject와 Observer 인터페이스를 정의하고 Subject의 구현에서 Observer 리스트를 관리하며 Observer 구현에서는 Subject의 상태 변경에 대한 처리를 정의해야 한다.

이렇게 구현하면 Subject와 Observer는 서로 독립적으로 변경될 수 있고, 새로운 Observer나 Subject를 추가 및 제거하는 것도 용이해진다.

 

간단한 구현

// 옵저버(Observer) 클래스
class Observer {
public:
    virtual void update() = 0;  // 상태 변화 알림 메서드
};

// 주제(Subject) 클래스
class Subject {
private:
    std::vector<Observer*> observers;  // 옵저버 목록
    int state;                         // 주제 객체의 상태

public:
    void attach(Observer* observer) {  // 옵저버 등록 메서드
        observers.push_back(observer);
    }

    void setState(int state) {         // 상태 설정 메서드
        this->state = state;
        notify();                      // 상태 변화 알리기
    }

    int getState() {                   // 상태 조회 메서드
        return state;
    }

    void notify() {                    // 상태 변화 알리기 메서드
        for (auto observer : observers) {
            observer->update();
        }
    }
};

옵저버 클래스와 주체 클래스를 정의하고 옵저버 클래스는 추상클래스로 옵저버들이 구현해야 할 update()메서드를 갖고 이를 통해 상태 변화를 알림

주체 클래스는 옵저버 리스트를 유지하며 상태 변화를 알리는 메서드들과 상태 값을 가짐

attach()메서드는 옵저버를 옵저버 목록에 추가하는 역할을 하고  setState()메서드는 상태 값을 설정 후 notify()메서드를 호출해 상태 변화를 알림

 

// 구체적인 옵저버 클래스
class ConcreteObserver : public Observer {
private:
    Subject* subject;  // 주제 객체

public:
    ConcreteObserver(Subject* subject) {
        this->subject = subject;
        subject->attach(this);  // 옵저버 등록
    }

    void update() override {  // 상태 변화 알림 메서드 구현
        std::cout << "Subject의 상태가 변화했습니다. 현재 상태: " << subject->getState() << std::endl;
    }
};

ConcreteObserver 클래스는 구체적인 옵저버 클래스로 옵저버 클래스를 상속받아 update 메서드를 구현함

생성자에서 주체 객체를 인자로 받아 attach 메서드를 호출해 옵저버로 등록함

 

간단하게 테스트하기 위해 main을 구현하면,

int main() {
    Subject subject;
    ConcreteObserver observer1(&subject);
    ConcreteObserver observer2(&subject);

    subject.setState(1);  // 상태 변화 알리기

    return 0;
}

주체에서 상태를 1로 설정하면 옵저버들의 update 메서드가 호출되어 아래와 같은 출력이 발생하게 된다.

 

'C++ > 디자인 패턴' 카테고리의 다른 글

상태(State) 패턴  (0) 2023.03.19
싱글턴(Singleton) 패턴  (0) 2023.03.12
경량(Flyweight) 패턴  (1) 2023.03.11
디자인 패턴  (0) 2023.03.11
명령(Command) 패턴  (0) 2023.03.11