1. 观察者模式在C++中的核心价值与应用场景
观察者模式(Observer Pattern)是设计模式中最常用的行为型模式之一,特别适合处理对象间一对多的依赖关系。当某个对象状态发生改变时,所有依赖它的对象都会自动收到通知并更新。这种机制在GUI事件处理、消息队列、实时数据监控等场景中尤为常见。
我在实际项目中最典型的应用案例是股票行情系统。当股票价格变动时,需要实时通知所有关注该股票的客户端更新界面。如果采用传统的轮询方式,不仅效率低下,还会造成资源浪费。而观察者模式通过注册-通知机制完美解决了这个问题。
2. 观察者模式的经典实现方案
2.1 基础类结构设计
标准的观察者模式包含两个核心角色:
- Subject(主题):维护观察者列表,提供添加/删除观察者的接口
- Observer(观察者):定义更新接口,供主题状态变化时调用
cpp复制// 观察者基类
class Observer {
public:
virtual ~Observer() = default;
virtual void update(const std::string& message) = 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 Stock : public Subject {
std::vector<Observer*> observers_;
std::string symbol_;
double price_;
public:
Stock(const std::string& symbol, double price)
: symbol_(symbol), price_(price) {}
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 {
std::string msg = symbol_ + " price changed to " + std::to_string(price_);
for (auto observer : observers_) {
observer->update(msg);
}
}
void setPrice(double price) {
if (price_ != price) {
price_ = price;
notify(); // 价格变化时自动通知观察者
}
}
};
// 具体观察者:客户端显示
class ClientDisplay : public Observer {
std::string name_;
public:
ClientDisplay(const std::string& name) : name_(name) {}
void update(const std::string& message) override {
std::cout << name_ << " received: " << message << std::endl;
}
};
3. 现代C++的优化实现
3.1 使用智能指针管理生命周期
原始指针容易造成内存泄漏,我们可以用shared_ptr和weak_ptr改进:
cpp复制class Subject {
std::vector<std::weak_ptr<Observer>> observers_;
public:
void attach(std::shared_ptr<Observer> observer) {
observers_.emplace_back(observer);
}
// ...其余实现类似
};
3.2 使用lambda简化回调
C++11后可以用function和bind替代继承:
cpp复制class Stock {
using Handler = std::function<void(const std::string&)>;
std::vector<Handler> handlers_;
public:
void attach(Handler handler) {
handlers_.push_back(handler);
}
void notify() {
std::string msg = /*...*/;
for (auto& handler : handlers_) {
handler(msg);
}
}
};
// 使用示例
Stock stock;
stock.attach([](const std::string& msg) {
std::cout << "Lambda handler: " << msg << std::endl;
});
4. 线程安全实现方案
4.1 基本的互斥锁保护
cpp复制#include <mutex>
class ThreadSafeStock : public Subject {
std::vector<std::weak_ptr<Observer>> observers_;
mutable std::mutex mtx_;
public:
void attach(std::shared_ptr<Observer> observer) override {
std::lock_guard<std::mutex> lock(mtx_);
observers_.emplace_back(observer);
}
void notify() override {
std::vector<std::shared_ptr<Observer>> aliveObservers;
{
std::lock_guard<std::mutex> lock(mtx_);
for (auto it = observers_.begin(); it != observers_.end(); ) {
if (auto observer = it->lock()) {
aliveObservers.push_back(observer);
++it;
} else {
it = observers_.erase(it);
}
}
}
std::string msg = getUpdateMessage();
for (auto& observer : aliveObservers) {
observer->update(msg);
}
}
};
4.2 使用读写锁优化性能
当读多写少时,可以用shared_mutex(C++17)提高并发性:
cpp复制#include <shared_mutex>
class ReadOptimizedStock : public Subject {
std::shared_mutex mtx_;
// ...其余成员
public:
void attach(std::shared_ptr<Observer> observer) override {
std::unique_lock lock(mtx_); // 写锁
observers_.emplace_back(observer);
}
void notify() override {
std::vector<std::shared_ptr<Observer>> aliveObservers;
{
std::shared_lock lock(mtx_); // 读锁
// ...收集存活观察者
}
// ...通知观察者
}
};
5. 实际项目中的经验总结
5.1 性能优化技巧
- 批量通知:当状态频繁变化时,可以积累变化后统一通知
cpp复制void setPrice(double price) {
if (price_ != price) {
price_ = price;
if (!batchMode_) {
notify();
}
}
}
void beginBatchUpdate() { batchMode_ = true; }
void endBatchUpdate() { batchMode_ = false; notify(); }
- 差异化通知:只通知关注特定变化的观察者
cpp复制enum class ChangeType { PRICE, VOLUME, ALL };
virtual void notify(ChangeType type = ChangeType::ALL);
5.2 常见问题排查
-
循环引用问题:
- 当Subject和Observer相互持有时,需使用weak_ptr打破循环
- 特别容易出现在观察者需要反向访问主题的情况
-
通知顺序依赖:
- 不同观察者可能有通知顺序要求
- 解决方案:为观察者添加优先级字段,通知时按优先级排序
-
死锁风险:
- 观察者的update方法中又调用主题的方法可能导致死锁
- 建议:在通知时释放锁后再调用观察者方法
6. 与其他模式的结合应用
6.1 观察者+工厂模式
通过工厂创建观察者,便于统一管理:
cpp复制class ObserverFactory {
public:
static std::shared_ptr<Observer> create(const std::string& type) {
if (type == "display") return std::make_shared<ClientDisplay>();
if (type == "logger") return std::make_shared<FileLogger>();
throw std::runtime_error("Unknown observer type");
}
};
6.2 观察者+中介者模式
当观察者逻辑复杂时,可以引入中介者协调:
cpp复制class NotificationMediator {
std::vector<std::weak_ptr<Observer>> observers_;
public:
void broadcast(const std::string& msg) {
// ...统一处理通知逻辑
}
};
class Stock {
NotificationMediator& mediator_;
public:
void setPrice(double price) {
// ...
mediator_.broadcast(updateMessage());
}
};
7. 测试策略与验证方法
7.1 单元测试示例
使用Google Test框架验证观察者模式:
cpp复制TEST(ObserverPatternTest, BasicNotification) {
auto stock = std::make_shared<Stock>("AAPL", 150.0);
auto display = std::make_shared<ClientDisplay>("TestDisplay");
testing::internal::CaptureStdout();
stock->attach(display);
stock->setPrice(155.0);
std::string output = testing::internal::GetCapturedStdout();
EXPECT_TRUE(output.find("AAPL price changed to 155") != std::string::npos);
}
TEST(ObserverPatternTest, MultiThreadSafety) {
auto stock = std::make_shared<ThreadSafeStock>("MSFT", 250.0);
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back([&stock, i]() {
auto observer = std::make_shared<ClientDisplay>("Thread" + std::to_string(i));
stock->attach(observer);
});
}
for (auto& t : threads) t.join();
EXPECT_EQ(stock->observerCount(), 10);
}
7.2 性能测试要点
- 单线程基准测试:测量通知1000个观察者的耗时
- 多线程竞争测试:模拟高并发场景下的线程安全
- 内存泄漏检测:使用Valgrind等工具验证资源释放
8. 替代方案与模式比较
8.1 与发布-订阅模式对比
| 特性 | 观察者模式 | 发布-订阅模式 |
|---|---|---|
| 耦合度 | 主题知道观察者 | 完全解耦 |
| 通信方式 | 直接方法调用 | 通过消息代理 |
| 适用场景 | 对象间直接通知 | 分布式系统通知 |
| 实现复杂度 | 相对简单 | 需要中间件支持 |
8.2 C++信号槽机制
Qt框架的信号槽是观察者模式的扩展实现:
cpp复制// Qt风格信号槽示例
class Stock : public QObject {
Q_OBJECT
public:
explicit Stock(QObject* parent = nullptr) : QObject(parent) {}
signals:
void priceChanged(double newPrice);
};
class Display : public QObject {
Q_OBJECT
public slots:
void onPriceChanged(double price) {
qDebug() << "New price:" << price;
}
};
// 连接方式
Stock stock;
Display display;
QObject::connect(&stock, &Stock::priceChanged,
&display, &Display::onPriceChanged);
9. 在大型项目中的架构建议
9.1 模块化设计原则
- 定义清晰的接口:将Observer和Subject接口放在独立头文件中
- 提供默认实现:为常见场景提供模板类
- 支持插件机制:允许动态加载观察者实现
9.2 性能关键系统优化
对于高频交易等对性能要求极高的系统:
- 无锁队列通知:使用boost::lockfree或moodycamel::ConcurrentQueue
- 内存池预分配:避免频繁内存分配
- 批量处理优化:合并多个更新事件
cpp复制template<typename T>
class HighFrequencySubject {
moodycamel::ConcurrentQueue<std::shared_ptr<Observer>> observers_;
// ...其他优化实现
};
10. 最新C++标准的特性利用
10.1 使用C++20协程实现异步通知
cpp复制#include <coroutine>
struct NotificationAwaiter {
Stock& stock_;
bool await_ready() const { return false; }
void await_suspend(std::coroutine_handle<> h) {
stock_.attach([h](auto&&) { h.resume(); });
}
void await_resume() {}
};
NotificationAwaiter operator co_await(Stock& stock) {
return {stock};
}
Task<> asyncExample(Stock& stock) {
co_await stock; // 等待股票价格变化
std::cout << "Price changed!" << std::endl;
}
10.2 使用C++23的sender/receiver
cpp复制#include <execution>
auto getPriceStream(Stock& stock) {
return std::execution::schedule(std::execution::seq)
| std::execution::let_value([&] {
return std::execution::just(stock.currentPrice());
});
}
// 使用示例
std::execution::submit(
getPriceStream(stock),
std::execution::make_receiver(
[](double price) { /* 处理价格 */ },
[](std::exception_ptr ep) { /* 处理错误 */ }
)
);
