1. 观察者模式基础与变体需求
观察者模式作为行为型设计模式的经典代表,在C++中常用于实现对象间的一对多依赖关系。当被观察对象状态变化时,所有依赖它的观察者都会自动收到通知。标准实现通常包含Subject(目标)和Observer(观察者)两个基类,通过虚函数实现通知机制。
但在实际工程中,标准观察者模式常面临几个痛点:
- 通知粒度难以控制(观察者可能被迫接收不关心的更新)
- 线程安全性问题(跨线程通知时的竞态条件)
- 性能瓶颈(大量观察者时的线性通知开销)
- 生命周期管理复杂(观察者提前析构导致的野指针问题)
这些痛点催生了多种变体实现。下面通过几个典型场景,展示如何针对特定需求改造标准观察者模式。
2. 基于事件分发的变体实现
2.1 事件类型标识方案
传统观察者模式中,观察者只能通过单一的update()接口接收通知。改进方案是引入事件类型标识,让观察者可以注册特定类型的事件:
cpp复制enum class EventType {
MOUSE_CLICK,
KEY_PRESS,
NETWORK_MSG
};
class EventObserver {
public:
virtual void onEvent(EventType type, const EventData& data) = 0;
virtual ~EventObserver() = default;
};
class EventSubject {
public:
void subscribe(EventType type, EventObserver* obs) {
observers_[type].push_back(obs);
}
void notify(EventType type, const EventData& data) {
for (auto* obs : observers_[type]) {
obs->onEvent(type, data);
}
}
private:
std::unordered_map<EventType, std::vector<EventObserver*>> observers_;
};
这种实现的关键优势:
- 观察者只需处理注册的事件类型
- 减少了不必要的通知调用
- 事件分类明确,便于调试
2.2 性能优化技巧
当事件类型较多时,unordered_map查找可能成为瓶颈。可以采用以下优化:
- 枚举哈希优化:为
EventType实现定制哈希函数
cpp复制struct EventTypeHash {
size_t operator()(EventType type) const {
return static_cast<size_t>(type);
}
};
- 预分配桶空间:根据事件类型数量预留空间
cpp复制observers_.reserve(static_cast<size_t>(EventType::COUNT));
- 批量通知优化:对高频事件使用批处理
cpp复制void batchNotify(EventType type, const std::vector<EventData>& batch) {
auto& obs_list = observers_[type];
for (auto* obs : obs_list) {
for (const auto& data : batch) {
obs->onEvent(type, data);
}
}
}
3. 线程安全的观察者模式实现
3.1 互斥锁方案
最简单的线程安全实现是引入互斥锁:
cpp复制class ThreadSafeSubject {
public:
void subscribe(Observer* obs) {
std::lock_guard<std::mutex> lock(mutex_);
observers_.insert(obs);
}
void unsubscribe(Observer* obs) {
std::lock_guard<std::mutex> lock(mutex_);
observers_.erase(obs);
}
void notify() {
std::lock_guard<std::mutex> lock(mutex_);
for (auto* obs : observers_) {
obs->update();
}
}
private:
std::mutex mutex_;
std::unordered_set<Observer*> observers_;
};
注意:这种实现存在死锁风险。如果观察者的
update()方法中又调用了subject的方法,可能导致递归锁死锁。
3.2 无锁队列方案
更高效的实现是使用无锁队列处理通知:
cpp复制class LockFreeSubject {
public:
void subscribe(Observer* obs) {
observers_.push_back(obs);
}
void notify() {
auto snapshot = observers_.load();
for (auto* obs : *snapshot) {
task_queue_.push([obs]{ obs->update(); });
}
}
private:
std::atomic<std::shared_ptr<std::vector<Observer*>>> observers_;
LockFreeTaskQueue task_queue_;
};
关键点:
- 使用
atomic保证observers列表的原子访问 - 通过任务队列解耦通知过程
- 观察者更新在独立线程中执行
4. 生命周期管理变体
4.1 弱引用解决方案
使用std::weak_ptr避免悬垂指针:
cpp复制class SafeObserver : public std::enable_shared_from_this<SafeObserver> {
public:
virtual void update() = 0;
};
class SafeSubject {
public:
void subscribe(std::weak_ptr<SafeObserver> obs) {
observers_.push_back(obs);
}
void notify() {
auto it = observers_.begin();
while (it != observers_.end()) {
if (auto obs = it->lock()) {
obs->update();
++it;
} else {
it = observers_.erase(it);
}
}
}
private:
std::vector<std::weak_ptr<SafeObserver>> observers_;
};
4.2 自动注销机制
结合RAII实现自动注销:
cpp复制class ScopedObserver {
public:
ScopedObserver(Subject* subj, Observer* obs)
: subject_(subj), observer_(obs) {
subject_->subscribe(observer_);
}
~ScopedObserver() {
subject_->unsubscribe(observer_);
}
private:
Subject* subject_;
Observer* observer_;
};
典型使用场景:
cpp复制{
Subject subject;
Observer obs;
ScopedObserver guard(&subject, &obs); // 自动注册
// ...
} // 作用域结束自动注销
5. 性能敏感场景的优化变体
5.1 内存池实现
高频通知场景下,可以预分配观察者内存:
cpp复制template <typename ObserverType>
class ObserverPool {
public:
template <typename... Args>
ObserverType* create(Args&&... args) {
return pool_.construct(std::forward<Args>(args)...);
}
void destroy(ObserverType* obs) {
pool_.destroy(obs);
}
private:
boost::object_pool<ObserverType> pool_;
};
5.2 事件合并策略
对高频更新事件,可以实现节流和防抖:
cpp复制class DebounceSubject {
public:
void requestNotify() {
if (!pending_) {
pending_ = true;
timer_.expires_after(100ms);
timer_.async_wait([this](auto...){
pending_ = false;
notify();
});
}
}
private:
bool pending_ = false;
asio::steady_timer timer_;
};
6. 混合观察者模式实现
结合多种变体优点的完整示例:
cpp复制class AdvancedSubject {
public:
using ObserverPtr = std::shared_ptr<Observer>;
void subscribe(EventType type, ObserverPtr obs) {
std::lock_guard lock(mutex_);
observers_[type].emplace_back(std::move(obs));
}
void notify(EventType type, const EventData& data) {
std::vector<ObserverPtr> snapshot;
{
std::lock_guard lock(mutex_);
auto it = observers_.find(type);
if (it == observers_.end()) return;
snapshot = it->second;
}
for (const auto& obs : snapshot) {
if (auto ptr = obs.lock()) {
ptr->onEvent(type, data);
}
}
}
private:
std::mutex mutex_;
std::unordered_map<
EventType,
std::vector<std::weak_ptr<Observer>>,
EventTypeHash
> observers_;
};
这个实现融合了:
- 事件类型分类
- 线程安全保护
- 弱引用生命周期管理
- 细粒度锁优化
7. 实际工程中的经验教训
-
避免在通知过程中修改观察者列表:这会导致迭代器失效。解决方案是先复制列表再通知。
-
处理观察者抛出的异常:
cpp复制try {
obs->update();
} catch (...) {
// 记录日志但继续通知其他观察者
}
- 跨模块使用时注意DLL边界:
- 确保Subject和Observer在同一个内存管理器作用域内
- 或者使用明确的工厂函数创建/销毁对象
- 性能分析要点:
- 使用profiler检测通知路径的热点
- 对高频事件考虑批处理或异步通知
- 在x86平台上注意false sharing问题
- 调试技巧:
- 为每个观察者添加唯一ID便于追踪
- 实现通知日志记录功能
- 使用RAII计数器统计活跃观察者数量