1. 观察者模式基础解析
观察者模式是设计模式中最常用的行为型模式之一,它定义了对象间一对多的依赖关系,当一个对象状态发生改变时,所有依赖它的对象都会自动收到通知并更新。在C++中实现观察者模式,我们需要理解几个核心概念:
- Subject(主题):维护观察者列表,提供注册和注销观察者的接口,状态变化时通知观察者
- Observer(观察者):定义更新接口,供主题通知时调用
- ConcreteSubject(具体主题):实现主题接口,存储具体状态,状态改变时通知观察者
- ConcreteObserver(具体观察者):实现观察者接口,保持对具体主题的引用
提示:在C++实现中要特别注意对象生命周期管理,避免观察者被销毁后主题仍尝试调用其方法。
2. 经典C++实现方案
2.1 基础接口设计
我们先定义观察者模式的核心接口:
cpp复制class Observer {
public:
virtual ~Observer() = default;
virtual void update() = 0;
};
class Subject {
public:
virtual ~Subject() = default;
virtual void attach(Observer* observer) = 0;
virtual void detach(Observer* observer) = 0;
virtual void notify() = 0;
};
2.2 具体实现示例
下面是一个气象站监控的具体实现:
cpp复制class WeatherStation : public Subject {
std::vector<Observer*> observers;
float temperature;
public:
void attach(Observer* observer) override {
observers.push_back(observer);
}
void detach(Observer* observer) override {
observers.erase(std::remove(observers.begin(), observers.end(), observer),
observers.end());
}
void notify() override {
for (auto observer : observers) {
observer->update();
}
}
void setTemperature(float temp) {
temperature = temp;
notify();
}
float getTemperature() const { return temperature; }
};
class Display : public Observer {
WeatherStation& station;
public:
Display(WeatherStation& station) : station(station) {
station.attach(this);
}
~Display() {
station.detach(this);
}
void update() override {
std::cout << "Temperature updated: "
<< station.getTemperature() << "°C\n";
}
};
3. 现代C++改进方案
3.1 使用智能指针管理生命周期
原始指针容易导致内存泄漏,我们可以用shared_ptr和weak_ptr改进:
cpp复制class Subject {
std::vector<std::weak_ptr<Observer>> observers;
void prune() {
observers.erase(
std::remove_if(observers.begin(), observers.end(),
[](const auto& wp) { return wp.expired(); }),
observers.end());
}
public:
void attach(std::shared_ptr<Observer> observer) {
observers.push_back(observer);
}
void notify() {
prune();
for (auto& wp : observers) {
if (auto observer = wp.lock()) {
observer->update();
}
}
}
};
3.2 使用模板实现通用观察者
cpp复制template <typename... Args>
class Observer {
public:
virtual ~Observer() = default;
virtual void update(Args... args) = 0;
};
template <typename... Args>
class Subject {
std::vector<std::weak_ptr<Observer<Args...>>> observers;
public:
void attach(std::shared_ptr<Observer<Args...>> observer) {
observers.push_back(observer);
}
void notify(Args... args) {
for (auto& wp : observers) {
if (auto observer = wp.lock()) {
observer->update(args...);
}
}
}
};
4. 实际应用中的问题与解决方案
4.1 线程安全问题
在多线程环境下,观察者模式需要额外保护:
cpp复制class ThreadSafeSubject : public Subject {
std::mutex mtx;
public:
void attach(Observer* observer) override {
std::lock_guard<std::mutex> lock(mtx);
Subject::attach(observer);
}
void notify() override {
std::lock_guard<std::mutex> lock(mtx);
Subject::notify();
}
};
4.2 性能优化技巧
- 批量通知:当状态频繁变化时,可以设置一个标记,只在特定时机通知
- 差异化通知:为观察者添加优先级或分类,只通知相关观察者
- 异步通知:将通知放入任务队列,避免阻塞主线程
cpp复制class AsyncSubject : public Subject {
std::queue<std::function<void()>> taskQueue;
std::mutex queueMutex;
std::condition_variable cv;
std::thread worker;
bool running = true;
void workerThread() {
while (running) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queueMutex);
cv.wait(lock, [this] { return !taskQueue.empty() || !running; });
if (!running && taskQueue.empty()) break;
task = std::move(taskQueue.front());
taskQueue.pop();
}
task();
}
}
public:
AsyncSubject() : worker(&AsyncSubject::workerThread, this) {}
~AsyncSubject() {
{
std::lock_guard<std::mutex> lock(queueMutex);
running = false;
}
cv.notify_all();
worker.join();
}
void notify() override {
{
std::lock_guard<std::mutex> lock(queueMutex);
taskQueue.push([this] { Subject::notify(); });
}
cv.notify_one();
}
};
5. 观察者模式在大型项目中的应用
5.1 事件系统设计
在游戏引擎中,观察者模式常用于事件系统:
cpp复制class Event {
public:
virtual ~Event() = default;
};
class EventDispatcher {
std::unordered_map<std::type_index,
std::vector<std::function<void(const Event&)>>> handlers;
public:
template <typename EventType>
void subscribe(std::function<void(const EventType&)> handler) {
handlers[typeid(EventType)].push_back(
[handler](const Event& e) {
handler(static_cast<const EventType&>(e));
});
}
template <typename EventType>
void dispatch(const EventType& event) {
auto it = handlers.find(typeid(EventType));
if (it != handlers.end()) {
for (auto& handler : it->second) {
handler(event);
}
}
}
};
5.2 GUI框架中的应用
在GUI框架中,观察者模式处理用户输入:
cpp复制class Button {
std::vector<std::function<void()>> clickHandlers;
public:
void onClick(std::function<void()> handler) {
clickHandlers.push_back(handler);
}
void press() {
for (auto& handler : clickHandlers) {
handler();
}
}
};
6. 观察者模式的替代方案
6.1 信号与槽机制
Qt风格的信号槽是观察者模式的变体:
cpp复制template <typename... Args>
class Signal {
std::vector<std::function<void(Args...)>> slots;
public:
void connect(std::function<void(Args...)> slot) {
slots.push_back(slot);
}
void emit(Args... args) {
for (auto& slot : slots) {
slot(args...);
}
}
};
// 使用示例
Signal<int, int> mouseMoved;
mouseMoved.connect([](int x, int y) {
std::cout << "Mouse moved to: " << x << ", " << y << "\n";
});
6.2 反应式编程
使用RxCpp等库实现反应式观察者:
cpp复制#include <rxcpp/rx.hpp>
void reactiveExample() {
auto values = rxcpp::observable<>::range(1, 5)
.map([](int v) { return v * 2; })
.filter([](int v) { return v > 5; });
values.subscribe(
[](int v) { std::cout << "OnNext: " << v << "\n"; },
[]() { std::cout << "OnCompleted\n"; });
}
7. 设计考量与最佳实践
-
接口设计原则:
- 保持Subject和Observer接口最小化
- 考虑添加获取变更详细信息的参数
- 为观察者添加标识符以便管理
-
生命周期管理:
- 使用weak_ptr打破循环引用
- 在析构函数中自动注销观察者
- 考虑添加观察者存在性检查
-
性能考量:
- 避免在通知过程中修改观察者列表
- 考虑使用更高效的数据结构存储观察者
- 对高频通知场景进行优化
-
扩展性设计:
- 支持观察者优先级
- 允许选择性通知
- 提供批量操作接口
cpp复制class AdvancedSubject : public Subject {
std::map<int, std::vector<Observer*>> priorityObservers;
public:
void attach(Observer* observer, int priority = 0) {
priorityObservers[priority].push_back(observer);
}
void notify() override {
for (auto& [priority, observers] : priorityObservers) {
for (auto observer : observers) {
observer->update();
}
}
}
};
在实际项目中,观察者模式最常见的坑是忘记注销观察者导致的内存泄漏。我曾在项目中遇到一个难以追踪的内存泄漏问题,最终发现是因为某个长期存在的主题持有了大量已销毁观察者的指针。解决方案是改用weak_ptr或者在观察者析构时自动注销自己。