在现代软件开发中,组件之间的解耦和事件的及时响应至关重要。使用 C++20 开发高并发系统时,我们经常需要一种机制,让一个对象(主题)的状态变化能够自动通知给多个依赖它的对象(观察者)。 观察者模式就是解决这类问题的利器,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当这个主题对象的状态发生改变时,所有观察者对象都将收到通知并更新自己。想象一下,我们用 Nginx 做反向代理,后端服务状态变化时,需要及时更新 upstream 的配置,就可以用观察者模式来实现。
经典观察者模式的结构
观察者模式主要包含以下几个角色:
- 主题(Subject):维护观察者列表,提供添加、删除观察者的方法,并在状态改变时通知观察者。
- 观察者(Observer):定义一个更新接口,用于接收主题的通知。
- 具体主题(ConcreteSubject):主题的具体实现,维护自身状态,并在状态改变时通知观察者。
- 具体观察者(ConcreteObserver):观察者的具体实现,接收主题的通知并执行相应的更新操作。
C++20 实现:更简洁、更高效
C++20 引入了许多新特性,例如 Concepts、Ranges 和 Coroutines,可以简化观察者模式的实现,并提高代码的可读性和性能。下面是一个使用 C++20 实现观察者模式的示例:
#include <iostream>
#include <vector>
#include <algorithm>
#include <memory>
// 观察者接口
class Observer {
public:
virtual void update(int value) = 0; // 纯虚函数,必须实现
virtual ~Observer() = default; // 虚析构函数,用于多态
};
// 主题类
class Subject {
public:
void attach(Observer* observer) {
observers_.push_back(observer);
}
void detach(Observer* observer) {
observers_.erase(std::remove(observers_.begin(), observers_.end(), observer), observers_.end());
}
void notify(int value) {
for (Observer* observer : observers_) {
observer->update(value);
}
}
void setValue(int value) {
value_ = value;
notify(value_);
}
private:
std::vector<Observer*> observers_; // 存储观察者指针
int value_ = 0;
};
// 具体观察者类
class ConcreteObserver : public Observer {
public:
ConcreteObserver(Subject* subject, const std::string& name) : subject_(subject), name_(name) {
subject_->attach(this);
}
~ConcreteObserver() override {
subject_->detach(this);
}
void update(int value) override {
std::cout << name_ << " received update: " << value << std::endl;
}
private:
Subject* subject_;
std::string name_;
};
int main() {
Subject subject;
ConcreteObserver observer1(&subject, "Observer 1");
ConcreteObserver observer2(&subject, "Observer 2");
subject.setValue(10); // 输出: Observer 1 received update: 10; Observer 2 received update: 10
subject.setValue(20); // 输出: Observer 1 received update: 20; Observer 2 received update: 20
return 0;
}
实战避坑:内存管理与线程安全
在使用观察者模式时,需要特别注意以下几个问题:
- 内存管理:观察者模式中,主题通常会持有观察者的指针或引用。如果观察者的生命周期短于主题,可能导致悬挂指针。可以使用智能指针(如
std::shared_ptr或std::unique_ptr)来管理观察者的生命周期,避免内存泄漏和悬挂指针。上面的示例中,使用了裸指针,实际项目中应使用智能指针。 - 线程安全:如果主题和观察者运行在不同的线程中,需要考虑线程安全问题。可以使用互斥锁(
std::mutex)来保护共享资源,例如观察者列表。在高并发场景下,可以考虑使用读写锁(std::shared_mutex)来提高性能。比如 Nginx 的 worker 进程处理请求,而主进程负责配置更新,就需要考虑线程安全。 - 循环依赖:避免主题和观察者之间形成循环依赖,否则可能导致无限循环。可以使用弱引用(
std::weak_ptr)来打破循环依赖。 - 过度通知:主题的状态频繁变化可能导致观察者收到过多的通知。可以使用节流(throttling)或去抖(debouncing)技术来减少通知的频率。
更进一步:使用函数对象和 Lambda 表达式
C++20 允许我们使用函数对象和 Lambda 表达式作为观察者,进一步简化代码:
#include <iostream>
#include <vector>
#include <functional>
class Subject {
public:
using Callback = std::function<void(int)>;
void attach(Callback callback) {
callbacks_.push_back(callback);
}
void detach(Callback callback) {
// 注意:此处比较函数对象可能无法直接工作,需要自定义比较器
// 建议使用观察者的 ID 或指针进行管理
// 这里仅为示例,实际使用需要更完善的实现
}
void notify(int value) {
for (const auto& callback : callbacks_) {
callback(value);
}
}
void setValue(int value) {
value_ = value;
notify(value_);
}
private:
std::vector<Callback> callbacks_;
int value_ = 0;
};
int main() {
Subject subject;
subject.attach([](int value) { // Lambda 表达式
std::cout << "Lambda Observer 1 received: " << value << std::endl;
});
auto observer2 = [](int value) { // Lambda 表达式
std::cout << "Lambda Observer 2 received: " << value << std::endl;
};
subject.attach(observer2);
subject.setValue(42); // 输出: Lambda Observer 1 received: 42; Lambda Observer 2 received: 42
return 0;
}
总结
观察者模式是一种强大的设计模式,可以帮助我们构建松耦合、可扩展的系统。在 C++20 中,我们可以利用新的语言特性,例如函数对象和 Lambda 表达式,来简化观察者模式的实现。然而,在使用观察者模式时,需要注意内存管理、线程安全和循环依赖等问题。 掌握观察者模式,能让你在面对复杂系统设计时,更加游刃有余。比如,在开发宝塔面板插件时,可以利用观察者模式,监听面板事件,实现插件的动态加载和卸载。
冠军资讯
半杯凉茶