S SmartDocs
Series: C++ cpp 490 lines · Updated 2026-04-03

observer.cpp

C++/Part5_進階主題/Ch24_設計模式與最佳實踐/observer.cpp

// observer.cpp
// 編譯指令:g++ -std=c++17 -Wall observer.cpp -o observer
//
// 本程式示範 Observer 模式:傳統類別繼承方式與現代 std::function 回呼方式

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
#include <unordered_map>
#include <memory>
#include <sstream>
#include <iomanip>
#include <cstdlib>
#include <ctime>

// ============================================================
// 第一部分:傳統 Observer 模式(類別繼承)
// ============================================================

// 觀察者介面
class Observer {
public:
    virtual ~Observer() = default;
    virtual void update(const std::string& event, const std::string& data) = 0;
};

// 主題(被觀察者)基底類別
class Subject {
    std::vector<Observer*> observers_;

public:
    virtual ~Subject() = default;

    void subscribe(Observer* observer) {
        observers_.push_back(observer);
    }

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

    void notify(const std::string& event, const std::string& data) {
        for (auto* obs : observers_) {
            obs->update(event, data);
        }
    }

    std::size_t observer_count() const { return observers_.size(); }
};

// ============================================================
// 具體實作:氣象站系統
// ============================================================

struct WeatherData {
    double temperature;
    double humidity;
    double pressure;

    std::string to_string() const {
        std::ostringstream oss;
        oss << std::fixed << std::setprecision(1)
            << "溫度=" << temperature << "°C, "
            << "濕度=" << humidity << "%, "
            << "氣壓=" << pressure << "hPa";
        return oss.str();
    }
};

class WeatherStation : public Subject {
    WeatherData data_{};

public:
    void set_measurements(double temp, double humidity, double pressure) {
        data_ = {temp, humidity, pressure};
        notify("weather_update", data_.to_string());
    }

    const WeatherData& get_data() const { return data_; }
};

// 具體觀察者 1:目前狀態顯示
class CurrentConditionsDisplay : public Observer {
    std::string name_;

public:
    explicit CurrentConditionsDisplay(const std::string& name) : name_(name) {}

    void update(const std::string& event, const std::string& data) override {
        if (event == "weather_update") {
            std::cout << "  [" << name_ << "] 目前天氣: " << data << "\n";
        }
    }
};

// 具體觀察者 2:統計顯示
class StatisticsDisplay : public Observer {
    double sum_ = 0;
    double min_ = 999;
    double max_ = -999;
    int count_ = 0;

public:
    void update(const std::string& event, const std::string& data) override {
        if (event == "weather_update") {
            // 簡化:從字串中解析溫度
            auto pos = data.find("溫度=");
            if (pos != std::string::npos) {
                // 跳過 "溫度=" 的 UTF-8 bytes
                std::string num_str = data.substr(pos + std::string("溫度=").size());
                double temp = std::stod(num_str);
                sum_ += temp;
                ++count_;
                if (temp < min_) min_ = temp;
                if (temp > max_) max_ = temp;

                std::cout << "  [統計] 平均=" << std::fixed << std::setprecision(1)
                          << (sum_ / count_) << "°C, 最低=" << min_
                          << "°C, 最高=" << max_ << "°C\n";
            }
        }
    }
};

// 具體觀察者 3:警報系統
class AlertDisplay : public Observer {
    double high_temp_threshold_;
    double low_temp_threshold_;

public:
    AlertDisplay(double low, double high)
        : high_temp_threshold_(high), low_temp_threshold_(low) {}

    void update(const std::string& event, const std::string& data) override {
        if (event == "weather_update") {
            auto pos = data.find("溫度=");
            if (pos != std::string::npos) {
                std::string num_str = data.substr(pos + std::string("溫度=").size());
                double temp = std::stod(num_str);
                if (temp >= high_temp_threshold_) {
                    std::cout << "  [警報] ⚠ 高溫警報!溫度 " << temp << "°C 超過閾值 "
                              << high_temp_threshold_ << "°C\n";
                } else if (temp <= low_temp_threshold_) {
                    std::cout << "  [警報] ⚠ 低溫警報!溫度 " << temp << "°C 低於閾值 "
                              << low_temp_threshold_ << "°C\n";
                }
            }
        }
    }
};

void demo_weather_station() {
    std::cout << "========================================\n";
    std::cout << "  傳統 Observer:氣象站系統\n";
    std::cout << "========================================\n\n";

    WeatherStation station;

    CurrentConditionsDisplay display1("室內顯示器");
    CurrentConditionsDisplay display2("手機 App");
    StatisticsDisplay stats;
    AlertDisplay alerts(5.0, 35.0);

    // 訂閱
    station.subscribe(&display1);
    station.subscribe(&display2);
    station.subscribe(&stats);
    station.subscribe(&alerts);

    std::cout << "觀察者數量: " << station.observer_count() << "\n\n";

    // 模擬氣象資料更新
    std::cout << "--- 更新 1 ---\n";
    station.set_measurements(28.5, 65.0, 1013.2);

    std::cout << "\n--- 更新 2 ---\n";
    station.set_measurements(36.2, 70.0, 1012.8);

    std::cout << "\n--- 更新 3(取消室內顯示器的訂閱後)---\n";
    station.unsubscribe(&display1);
    std::cout << "觀察者數量: " << station.observer_count() << "\n";
    station.set_measurements(3.0, 40.0, 1015.0);

    std::cout << "\n";
}

// ============================================================
// 第二部分:現代 Observer(使用 std::function 回呼)
// ============================================================

class EventSystem {
public:
    using EventId = int;
    using Callback = std::function<void(const std::string&)>;

    // 訂閱事件,回傳訂閱 ID(用於取消訂閱)
    EventId on(const std::string& event_name, Callback callback) {
        EventId id = next_id_++;
        listeners_[event_name].push_back({id, std::move(callback)});
        return id;
    }

    // 取消訂閱
    void off(const std::string& event_name, EventId id) {
        auto it = listeners_.find(event_name);
        if (it == listeners_.end()) return;

        auto& list = it->second;
        list.erase(
            std::remove_if(list.begin(), list.end(),
                [id](const Listener& l) { return l.id == id; }),
            list.end());
    }

    // 發送事件
    void emit(const std::string& event_name, const std::string& data = "") {
        auto it = listeners_.find(event_name);
        if (it == listeners_.end()) return;

        for (const auto& listener : it->second) {
            listener.callback(data);
        }
    }

    // 訂閱一次性事件
    EventId once(const std::string& event_name, Callback callback) {
        EventId id = next_id_++;
        auto weak_this = this;
        listeners_[event_name].push_back({id,
            [weak_this, event_name, id, cb = std::move(callback)](const std::string& data) {
                cb(data);
                weak_this->off(event_name, id);
            }});
        return id;
    }

    std::size_t listener_count(const std::string& event_name) const {
        auto it = listeners_.find(event_name);
        return (it != listeners_.end()) ? it->second.size() : 0;
    }

private:
    struct Listener {
        EventId id;
        Callback callback;
    };

    std::unordered_map<std::string, std::vector<Listener>> listeners_;
    EventId next_id_ = 0;
};

void demo_event_system() {
    std::cout << "========================================\n";
    std::cout << "  現代 Observer:EventSystem\n";
    std::cout << "========================================\n\n";

    EventSystem events;

    // 使用 lambda 訂閱
    auto id1 = events.on("user:login", [](const std::string& data) {
        std::cout << "  [安全系統] 使用者登入: " << data << "\n";
    });

    auto id2 = events.on("user:login", [](const std::string& data) {
        std::cout << "  [歡迎訊息] 歡迎回來, " << data << "!\n";
    });

    events.on("user:login", [](const std::string& data) {
        std::cout << "  [日誌系統] 記錄登入事件: " << data << "\n";
    });

    events.on("user:logout", [](const std::string& data) {
        std::cout << "  [安全系統] 使用者登出: " << data << "\n";
    });

    // 一次性事件
    events.once("app:start", [](const std::string&) {
        std::cout << "  [初始化] 應用程式首次啟動!\n";
    });

    // 觸發事件
    std::cout << "--- 觸發 app:start ---\n";
    events.emit("app:start");
    std::cout << "--- 再次觸發 app:start(once 已取消)---\n";
    events.emit("app:start");

    std::cout << "\n--- 觸發 user:login ---\n";
    events.emit("user:login", "小明");

    // 取消訂閱
    std::cout << "\n取消安全系統的訂閱 (id=" << id1 << ")\n";
    events.off("user:login", id1);

    std::cout << "--- 再次觸發 user:login ---\n";
    events.emit("user:login", "小華");

    std::cout << "\n--- 觸發 user:logout ---\n";
    events.emit("user:logout", "小華");

    (void)id2;
    std::cout << "\n";
}

// ============================================================
// 第三部分:型別安全的事件系統(模板版本)
// ============================================================

template<typename... Args>
class Signal {
public:
    using SlotId = int;
    using SlotType = std::function<void(Args...)>;

    SlotId connect(SlotType slot) {
        SlotId id = next_id_++;
        slots_.push_back({id, std::move(slot)});
        return id;
    }

    void disconnect(SlotId id) {
        slots_.erase(
            std::remove_if(slots_.begin(), slots_.end(),
                [id](const Slot& s) { return s.id == id; }),
            slots_.end());
    }

    void emit(Args... args) {
        for (const auto& slot : slots_) {
            slot.callback(args...);
        }
    }

    std::size_t slot_count() const { return slots_.size(); }

private:
    struct Slot {
        SlotId id;
        SlotType callback;
    };

    std::vector<Slot> slots_;
    SlotId next_id_ = 0;
};

// 按鈕 UI 元件範例
class Button {
    std::string label_;

public:
    Signal<> clicked;                          // 無參數信號
    Signal<int, int> right_clicked;            // 帶座標的右鍵點擊
    Signal<const std::string&> text_changed;   // 文字變更

    explicit Button(const std::string& label) : label_(label) {}

    void click() {
        std::cout << "  [Button '" << label_ << "'] 被點擊\n";
        clicked.emit();
    }

    void right_click(int x, int y) {
        std::cout << "  [Button '" << label_ << "'] 右鍵點擊 (" << x << ", " << y << ")\n";
        right_clicked.emit(x, y);
    }

    void set_text(const std::string& text) {
        label_ = text;
        text_changed.emit(text);
    }
};

void demo_signal_slot() {
    std::cout << "========================================\n";
    std::cout << "  型別安全的 Signal/Slot 系統\n";
    std::cout << "========================================\n\n";

    Button btn("確認");

    // 連接信號
    btn.clicked.connect([]() {
        std::cout << "    → 處理點擊事件\n";
    });

    btn.clicked.connect([]() {
        std::cout << "    → 播放點擊音效\n";
    });

    auto ctx_id = btn.right_clicked.connect([](int x, int y) {
        std::cout << "    → 顯示右鍵選單在 (" << x << ", " << y << ")\n";
    });

    btn.text_changed.connect([](const std::string& text) {
        std::cout << "    → 按鈕文字變更為: \"" << text << "\"\n";
    });

    // 觸發事件
    btn.click();
    std::cout << "\n";

    btn.right_click(100, 200);
    std::cout << "\n";

    btn.set_text("送出");
    std::cout << "\n";

    // 斷開右鍵選單
    btn.right_clicked.disconnect(ctx_id);
    std::cout << "斷開右鍵選單後:\n";
    btn.right_click(50, 50);
    std::cout << "\n";
}

// ============================================================
// 第四部分:訂閱者生命週期管理
// ============================================================

class ManagedObserver {
    std::vector<std::pair<EventSystem*, std::pair<std::string, EventSystem::EventId>>> subscriptions_;

public:
    void subscribe(EventSystem& events, const std::string& event_name,
                   EventSystem::Callback callback) {
        auto id = events.on(event_name, std::move(callback));
        subscriptions_.push_back({&events, {event_name, id}});
    }

    // 自動取消所有訂閱
    ~ManagedObserver() {
        for (auto& [system, sub] : subscriptions_) {
            system->off(sub.first, sub.second);
        }
        std::cout << "  [ManagedObserver] 已自動取消 "
                  << subscriptions_.size() << " 個訂閱\n";
    }

    ManagedObserver() = default;
    ManagedObserver(const ManagedObserver&) = delete;
    ManagedObserver& operator=(const ManagedObserver&) = delete;
};

void demo_lifecycle_management() {
    std::cout << "========================================\n";
    std::cout << "  觀察者生命週期管理\n";
    std::cout << "========================================\n\n";

    EventSystem events;

    events.on("data:update", [](const std::string& data) {
        std::cout << "  [永久訂閱者] 收到: " << data << "\n";
    });

    std::cout << "--- 建立臨時觀察者 ---\n";
    {
        ManagedObserver temp_observer;
        temp_observer.subscribe(events, "data:update", [](const std::string& data) {
            std::cout << "  [臨時訂閱者] 收到: " << data << "\n";
        });

        std::cout << "觸發事件(臨時觀察者存在時):\n";
        events.emit("data:update", "第一次更新");

        std::cout << "\n--- 臨時觀察者即將銷毀 ---\n";
    }

    std::cout << "\n觸發事件(臨時觀察者已銷毀後):\n";
    events.emit("data:update", "第二次更新");
    std::cout << "  (只有永久訂閱者收到)\n\n";
}

// ============================================================
// 主程式
// ============================================================

int main() {
    std::cout << "╔══════════════════════════════════════╗\n";
    std::cout << "║  設計模式:Observer 模式               ║\n";
    std::cout << "╚══════════════════════════════════════╝\n\n";

    demo_weather_station();
    demo_event_system();
    demo_signal_slot();
    demo_lifecycle_management();

    std::cout << "=== 程式結束 ===\n";
    return 0;
}

Related articles