C++ Observer 패턴

객체 상태 변화를 구독자들에게 자동으로 통지하는 이벤트 시스템입니다. std::function 기반의 현대적 구현을 포함합니다.

Gist
#include <iostream>
#include <functional>
#include <unordered_map>
#include <vector>
#include <string>
#include <memory>
#include <algorithm>

// ── 이벤트 타입 정의 ──────────────────────────────────────────────────────────
enum class MarketEvent {
    PriceChanged,
    VolumeChanged,
    MarketClosed,
};

// ── 이벤트 데이터 페이로드 ────────────────────────────────────────────────────
struct PricePayload  { std::string symbol; double oldPrice; double newPrice; };
struct VolumePayload { std::string symbol; long   volume; };
struct ClosePayload  { std::string exchange; };

// ── 핸들러 ID (구독 취소에 사용) ──────────────────────────────────────────────
using HandlerId = std::size_t;

// ── 제네릭 이벤트 버스 ────────────────────────────────────────────────────────
template<typename EventEnum, typename... Payloads>
class EventBus {
public:
    // 이벤트별 핸들러를 std::function 으로 저장
    template<typename Payload>
    HandlerId subscribe(EventEnum event, std::function<void(const Payload&)> handler) {
        HandlerId id = nextId_++;
        auto& bucket = handlers_[static_cast<int>(event)];
        bucket.emplace_back(id, [h = std::move(handler)](const void* data) {
            h(*static_cast<const Payload*>(data));
        });
        return id;
    }

    // 특정 핸들러 구독 취소
    void unsubscribe(EventEnum event, HandlerId id) {
        auto& bucket = handlers_[static_cast<int>(event)];
        bucket.erase(
            std::remove_if(bucket.begin(), bucket.end(),
                [id](const auto& pair) { return pair.first == id; }),
            bucket.end());
    }

    // 이벤트 발행 — 등록된 모든 핸들러에 페이로드 전달
    template<typename Payload>
    void publish(EventEnum event, const Payload& payload) {
        auto it = handlers_.find(static_cast<int>(event));
        if (it == handlers_.end()) return;
        for (auto& [id, fn] : it->second)
            fn(static_cast<const void*>(&payload));
    }

private:
    using ErasedHandler = std::pair<HandlerId, std::function<void(const void*)>>;
    std::unordered_map<int, std::vector<ErasedHandler>> handlers_;
    HandlerId nextId_{0};
};

// ── 주식 시장 데이터 피드 (Subject) ──────────────────────────────────────────
class StockFeed {
public:
    explicit StockFeed(EventBus<MarketEvent>& bus) : bus_(bus) {}

    void updatePrice(const std::string& symbol, double price) {
        double old = prices_[symbol];
        prices_[symbol] = price;
        bus_.publish(MarketEvent::PriceChanged,
                     PricePayload{symbol, old, price});
    }

    void updateVolume(const std::string& symbol, long vol) {
        bus_.publish(MarketEvent::VolumeChanged,
                     VolumePayload{symbol, vol});
    }

    void closeMarket(const std::string& exchange) {
        bus_.publish(MarketEvent::MarketClosed, ClosePayload{exchange});
    }

private:
    EventBus<MarketEvent>& bus_;
    std::unordered_map<std::string, double> prices_;
};

// ── 사용 예시 ─────────────────────────────────────────────────────────────────
int main() {
    EventBus<MarketEvent> bus;
    StockFeed feed(bus);

    // 가격 변동 구독 — 알림 대시보드
    auto dashId = bus.subscribe<PricePayload>(
        MarketEvent::PriceChanged,
        [](const PricePayload& p) {
            std::cout << "[대시보드] " << p.symbol
                      << " 가격 변동: " << p.oldPrice
                      << " → " << p.newPrice << '\n';
        });

    // 가격 변동 구독 — 알람 시스템 (5% 이상 변동 시 경고)
    bus.subscribe<PricePayload>(
        MarketEvent::PriceChanged,
        [](const PricePayload& p) {
            if (p.oldPrice > 0) {
                double pct = (p.newPrice - p.oldPrice) / p.oldPrice * 100.0;
                if (std::abs(pct) >= 5.0)
                    std::cout << "[경보] " << p.symbol
                              << " 급변동 " << pct << "%!\n";
            }
        });

    // 거래량 구독
    bus.subscribe<VolumePayload>(
        MarketEvent::VolumeChanged,
        [](const VolumePayload& v) {
            std::cout << "[거래량] " << v.symbol
                      << " : " << v.volume << "주\n";
        });

    // 시장 종료 구독
    bus.subscribe<ClosePayload>(
        MarketEvent::MarketClosed,
        [](const ClosePayload& c) {
            std::cout << "[종료] " << c.exchange << " 장 마감\n";
        });

    feed.updatePrice("AAPL", 0.0);   // 초기화
    feed.updatePrice("AAPL", 182.50);
    feed.updatePrice("AAPL", 195.10); // 6.9% 급등 → 알람 발생

    feed.updateVolume("AAPL", 45'000'000);

    // 대시보드 구독 해제 후 추가 이벤트
    bus.unsubscribe(MarketEvent::PriceChanged, dashId);
    feed.updatePrice("AAPL", 196.00); // 알람만 수신

    feed.closeMarket("NASDAQ");
}