彻底理解C++中的观察者模式!

来源:码农的荒岛求生 嵌入式技术 3 次阅读
摘要:你是一名C++程序员,负责开发公司的核心的股票交易系统,老板见到你都得喊一声哥,系统的核心是一个Stock类,存储股票的实时价格: class Stock { private:     std::string symbol_;     double price_; public:     void setPrice(double price) { price_ = price; }     dou

你是一名C++程序员,负责开发公司的核心的股票交易系统,老板见到你都得喊一声哥,系统的核心是一个Stock类,存储股票的实时价格:

class Stock {
private:
    std::string symbol_;
    double price_;
public:
    void setPrice(double price) { price_ = price; }
    double getPrice() const { return price_; }
};

现在有三个模块需要关注股价变化:交易界面要显示最新价格;价格超过阈值时预警模块要发出警报;每次价格变动日志模块要记录日志。

现在问题来了:当股价变化时,这三个模块怎么知道?

轮询检查

你的第一个想法很直接:让每个模块不停地查询股价。

// 交易界面的代码
void TradingUI::run() {
    double lastPrice = 0;
    while (true) {
        double currentPrice = stock_->getPrice();
        if (currentPrice != lastPrice) {
            updateDisplay(currentPrice);
            lastPrice = currentPrice;
        }
        sleep(1);  // 每1毫秒检查一次
    }
}

预警模块和日志模块也写了类似的代码,各自不停地轮询。

你运行了一下,功能是对的,但很快你老板见到你不叫哥了。

打开任务管理器,CPU占用率飙升,三个模块同时轮询,更糟的是,如果股价在两次轮询之间变化了两次(比如100→105→102),中间那次105的变化就丢失了。

这种方法既浪费资源,又可能丢失事件。

你需要一种更好的方法:让Stock主动通知关心它的模块,而不是让模块来问。

硬编码回调

聪明的你很快想到了办法:在Stock内部直接调用需要通知的模块。

class Stock {
private:
    double price_;
    TradingUI* ui_;
    AlertSystem* alert_;
    Logger* logger_;
    
public:
    void setPrice(double price) {
        price_ = price;
        // 直接通知各个模块
        ui_->onPriceChanged(price);
        alert_->onPriceChanged(price);
        logger_->onPriceChanged(price);
    }
};

这下不用轮询了!股价一变,三个模块立刻收到通知。

你测试了一下,完美,CPU占用率降到几乎为零,也不会丢失任何变化。

但一周后,问题来了。

产品经理说:"我们要加一个新功能——价格图表模块,也需要监听股价变化。"

你打开Stock类,加了一行:

void setPrice(double price) {
    price_ = price;
    ui_->onPriceChanged(price);
    alert_->onPriceChanged(price);
    logger_->onPriceChanged(price);
    chart_->onPriceChanged(price);  // 新加的
}

又过了一周,产品经理说:"预警模块下线了,不需要通知它了。",WTF,你又打开Stock类,删掉了一行。

你开始感到不对劲:每次增删一个监听者,都要修改Stock类的代码。

Stock是核心类,改动它有风险,而且它现在"知道"了所有监听者的存在,耦合度太高了。

问题又暴露了:Stock和监听者之间是硬编码的依赖,无法动态增删。

你需要一种更更优的方法:让Stock不知道具体有谁在监听,但能通知所有人。

观察者列表

你灵机一动一拍大腿:用一个列表来存储所有监听者!

首先,定义一个统一的接口:

class IPriceObserver {
public:
    virtual void onPriceChanged(double newPrice) = 0;
    virtual ~IPriceObserver() = default;
};

然后,让所有监听者实现这个接口:

class TradingUI : public IPriceObserver {
public:
    void onPriceChanged(double newPrice) override {
        updateDisplay(newPrice);
    }
};

class AlertSystem : public IPriceObserver {
public:
    void onPriceChanged(double newPrice) override {
        if (newPrice > threshold_) triggerAlert();
    }
};

最后,Stock类只维护一个观察者列表:

class Stock {
private:
    double price_;
    std::vector<IPriceObserver*> observers_;
    
public:
    void addObserver(IPriceObserver* observer) {
        observers_.push_back(observer);
    }
    
    void removeObserver(IPriceObserver* observer) {
        // 从列表中移除
        observers_.erase(
            std::remove(observers_.begin(), observers_.end(), observer),
            observers_.end()
        );
    }
    
    void setPrice(double price) {
        price_ = price;
        // 通知所有观察者
        for (auto* observer : observers_) {
            observer->onPriceChanged(price);
        }
    }
};

你测试了一下:

Stock stock;
TradingUI ui;
AlertSystem alert;
Logger logger;

stock.addObserver(&ui);
stock.addObserver(&alert);
stock.addObserver(&logger);

stock.setPrice(100.5);  // 三个模块同时收到通知!

// 下线预警模块
stock.removeObserver(&alert);

stock.setPrice(101.0);  // 只有ui和logger收到通知

完美!现在增删监听者不需要修改Stock类的代码了。

但随着系统规模扩大,你又发现了一个新的麻烦。

多对多的监听关系变得复杂

系统不只有Stock一个被观察者了,现在有三种数据源:

class Stock { ... };      // 股价
class Index { ... };      // 大盘指数  
class Exchange { ... };   // 汇率

每种数据源都要定义自己的观察者接口:

class IPriceObserver { virtual void onPriceChanged(double) = 0; };
class IIndexObserver { virtual void onIndexChanged(double) = 0; };
class IExchangeObserver { virtual void onExchangeChanged(double) = 0; };

AlertSystem需要监听所有这三种数据——它得实现三个接口:

class AlertSystem : public IPriceObserver, 
                    public IIndexObserver, 
                    public IExchangeObserver {
    void onPriceChanged(double price) override { ... }
    void onIndexChanged(double index) override { ... }
    void onExchangeChanged(double rate) override { ... }
};

注册时,AlertSystem还得知道每个被观察者的存在:

stock.addObserver(&alert);
index.addObserver(&alert);
exchange.addObserver(&alert);

问题又又又暴露了:

  1. 每新增一种数据源,就要新增一个接口,AlertSystem就要多实现一个接口
  2. AlertSystem必须知道所有被观察者的存在,耦合度高
  3. 如果TradingUI也要监听这三种数据,同样的代码再写一遍

你揪着头上硕果仅存的几根头发思考:有没有办法统一所有的事件,让观察者不需要知道被观察者的具体类型?

你拍着已经肿了的大腿又想到一个巧妙的办法:引入一个中间人,让双方都只和中间人打交道。

class EventBus {
private:
    // 事件名 → 回调函数列表
    std::map<std::string, std::vector<std::function<void(double)>>> subscribers_;
    
public:
    // 订阅事件
    void subscribe(const std::string& event, std::function<void(double)> callback) {
        subscribers_[event].push_back(callback);
    }
    
    // 发布事件
    void publish(const std::string& event, double data) {
        if (subscribers_.find(event) != subscribers_.end()) {
            for (auto& callback : subscribers_[event]) {
                callback(data);
            }
        }
    }
};

// 全局事件总线
EventBus g_eventBus;

现在,Stock不需要知道谁在监听:

class Stock {
private:
    double price_;
public:
    void setPrice(double price) {
        price_ = price;
        g_eventBus.publish("stock.price.changed", price);  // 只管发布
    }
};

监听者也不需要知道Stock的存在,甚至不需要实现任何接口:

这种"一个对象状态变化,自动通知所有依赖它的对象"的模式,就是所谓的观察者模式(Observer Pattern)。

评论区

登录后即可参与讨论

立即登录