系列: C++
cpp
490 行
· 更新於 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;
}
相關文章
C++
c
更新於 2026-07-21
deviceAlpha.h
deviceAlpha.h — c source code from the C++ learning materials (C++/Mavis_Homework/FinalProject/deviceAlpha.h).
閱讀文章 →
C++
c
更新於 2026-07-21
finalproject.c
finalproject.c — c source code from the C++ learning materials (C++/Mavis_Homework/FinalProject/finalproject.c).
閱讀文章 →
C++
cpp
更新於 2026-07-21
finalproject.cpp
finalproject.cpp — cpp source code from the C++ learning materials (C++/Mavis_Homework/FinalProject/finalproject.cpp).
閱讀文章 →
C++
c
更新於 2026-07-21
deviceAlpha.h
deviceAlpha.h — c source code from the C++ learning materials (C++/Mavis_Homework/Lab8/deviceAlpha.h).
閱讀文章 →
C++
c
更新於 2026-07-21
lab8.c
lab8.c — c source code from the C++ learning materials (C++/Mavis_Homework/Lab8/lab8.c).
閱讀文章 →
C++
cpp
更新於 2026-07-21
lab8.cpp
lab8.cpp — cpp source code from the C++ learning materials (C++/Mavis_Homework/Lab8/lab8.cpp).
閱讀文章 →