[C/C++] 옵저버 패턴(Observer Pattern)

할랑말랑·2026년 3월 18일

C/C++

목록 보기
28/45

옵저버 패턴(Observer Pattern)

한 객체의 상태가 변할 때, 그 객체에 의존하는 다른 객체들에게 자동으로 알림을 보내는 디자인 패턴

옵저버 또는 리스너(listener)라 불리는 하나 이상의 객체를 관찰 대상이 되는 객체에 등록시킨다. 그리고 각각의 옵저버들은 관찰 대상인 객체가 발생시키는 이벤트를 받아 처리한다.

  • Subject : 상태를 관리하고, 변경되었음을 옵저버에게 알린다.
  • Observer : Subject를 관찰하며 상태 변경 시 반응

1. 특징

  • 일대다(One-to-Many) 관계
    하나의 Subject가 변경되면 여러 Observer가 동시에 반응
    Observer의 개수는 동적으로 변할 수 있음
    Subject는 몇 개의 Observer가 있는지 알 필요 없음
  • 느슨한 결합 (Loose Coupling)
  • 자동 동기화

2. 장점

  • 개방-폐쇄 원칙 (OCP) 준수
  • 런타임 동적 구성
  • 관심사의 분리

3. 단점

  • 업데이트 순서 보장 안 됨
  • 옵저버 패턴을 자주 구성하면 구조와 동작을 알아보기 힘들어져 코드 복잡도가 증가
  • 다수의 옵저버 객체를 등록 이후 해지하지 않는다면 메모리 누수가 발생

채팅 예시

  • ChattingObserver -> 인터페이스 - 관찰자
  • User -> ChattingObserver 상속
  • ChattingSystem -> Subject(주체)
#include <iostream>
#include <vector>
#include <string>
#include <algorithm> // for std::remove

using namespace std;

// 옵저버의 인터페이스 (Observer)
class ChattingObserver
{
public:
    virtual ~ChattingObserver() = default;
    virtual void Update(const std::string log) = 0; // 주제로부터 업데이트를 받는 메서드
    virtual std::string getName() const = 0;
};

// 구체적인 옵저버 (Concrete Observer)
class User : public ChattingObserver
{
private:
    std::string name;
    std::vector<std::string> chatlog;

public:
    User(std::string _name) : name(_name) {}
    ~User() override {};

    // 주제(ChattingSystem)가 이 메서드를 호출하여 상태를 업데이트함
    void Update(const std::string log) override
    {
        chatlog.push_back(log);
    }

    std::string getName() const noexcept override
    {
        return name;
    }

    void Print()
    {
        cout << "========== " << name << "의 채팅 로그 ==========" << endl;
        for (const auto& message : chatlog)
        {
            cout << message << endl;
        }
        cout << "=========================================\n" << endl;
    }
};

// 주제 클래스 (Subject)
class ChattingSystem
{
private:
    std::vector<ChattingObserver*> users; // 옵저버(User)들의 목록

public:
    // 옵저버를 등록
    void Attach(ChattingObserver* _ob)
    {
        users.push_back(_ob);
        sendMessage(_ob->getName(), "님이 입장했습니다.");
    }

    // 옵저버를 제거
    void Detach(ChattingObserver* user)
    {
        notify(user->getName() + "님이 퇴장했습니다.");
        // erase-remove idiom을 사용하여 안전하게 제거
        users.erase(std::remove(users.begin(), users.end(), user), users.end());
    }

    // 등록된 모든 옵저버에게 알림을 보냄
    void notify(std::string _log)
    {
        for (ChattingObserver* user : users)
        {
            user->Update(_log);
        }
    }

    void sendMessage(const std::string& senderName, const std::string& message)
    {
        notify(senderName + " : " + message);
    }
};

int main()
{
    // 주제(채팅 시스템) 생성
    ChattingSystem chat;

    // 옵저버(유저)들 생성
    User user1("감자");
    User user2("동감자");
    User user3("고구마");

    // 유저들을 채팅 시스템에 등록(Attach)
    chat.Attach(&user1);
    chat.sendMessage(user1.getName(), "누구없나.");

    chat.Attach(&user2);
    chat.sendMessage(user2.getName(), "접니다.");

    chat.Attach(&user3);
    chat.sendMessage(user3.getName(), "반갑습니다.");

    // 각 유저의 채팅 로그 출력
    user1.Print();
    user2.Print();
    user3.Print();

    return 0;
}

  • 주제(Subject)와 옵저버(Observer)의 분리 : ChattingSystem은 자신을 구독하는 객체가 User 클래스인지, 아니면 다른 어떤 클래스인지 전혀 알 필요가 없다. 그저 ChattingObserver 인터페이스를 구현했다는 사실만 알면 된다. 마찬가지로 User는 ChattingSystem의 내부 구조를 알 필요 없이 그저 Update 신호를 받기만 한다.
  • 느슨한 결합(Loose Coupling) : 이처럼 주제와 옵저버가 서로에 대해 최소한의 정보만 알고 있기 때문에, 시스템의 유연성이 크게 향상된다.
  • 자동 업데이트 전파 : ChattingSystem에서 sendMessage가 호출되면, notify를 통해 등록된 모든 User들에게 메시지가 자동으로 전파된다. main 함수에서 각 유저 객체를 일일이 찾아다니며 메시지를 전달할 필요가 없다.

0개의 댓글