背景
假如我们现在有这样一个场景:市场上的股票价格不定时变化,而后台监控者和广告想要实时获取股票信息,我们应该怎么做?
显然在这个场景里,我们有这样一个设计
- 一个股票类不时更新股票价格
- 另外有两个类Monitor和BillBoard实时接收股票价格的更新并显示出来
基于以上想法,我们先写出一版代码出来
class Monitor {public:void printPrice(int price) {std::cout << "monitor price >>> " << price << std::endl;}
};class BillBoard {public:void displayPrice(int price) {std::cout << "BillBoard price >>> " << price << std::endl;}
};
class Stock {public://实时更新价格,并价格信息反馈给Monitor和BillBoardvoid set_price(int price) {price_ = price;monitor_->printPrice(price);billboard_->displayPrice(price);}private:int price_;std::unique_ptr<Monitor> monitor_;std::unique_ptr<BillBoard> billboard_;
};
void test() {Stock stock;stock.set_price(20);
}
上述代码显然是可以运行的,当Stock修改价格信息时,Monitor和BillBoard可以接受到Stock发送给它的价格信息
但上述代码设计的缺陷很明显
- Stock类和其余两个类之间的耦合度过高,假如我们还有其他的类也想要实时获取股票信息,那么每次就都得修改Stock类的代码
- 在实际场景里Stock不可能知道想要获取股票信息的类的方法具体是什么,也就是说printPrice方法和displayPrice对于Stock并不是事先知道的
观察者模式
初步设计
基于上述第二个缺陷,我们可以设计一个虚函数,将Monitor和BillBoard的打印股票信息的方法抽象成一个统计的接口,如下所示
class Observer {public:virtual void update(int) = 0;private:
};class Monitor : public Observer {public:void printPrice(int price) {std::cout << "monitor >>> " << price << std::endl;}void update(int price) override { printPrice(price); }
};class BillBoard : public Observer {public:void displayPrice(int price) {std::cout << "billoard >>> " << price << std::endl;}void update(int price) override { displayPrice(price); }
};
这样,每次新增一个订阅者的时候,Stock就知道调用哪个方法来发送股票信息了
class Stock {public:void set_price(int price) {price_ = price;observer->update(price);}private:int price_;Observer * observer;
};
为了能够一次性通知所有订阅者,我们可以使用一个容器保存所有的订阅者,然后遍历发送消息,像这样
class Stock {public:void notify(int price) {for (auto &obj : observerlist_) {obj->update(price);}}void set_price(int price) {price_ = price;notify(price);}private:int price_;std::list<Observer *> observerlist_;
};
当然,想要将消息发送出去,保存消息的容器当然要有订阅者对象才行,因此我们需要设计两个方法用于添加订阅者和撤销订阅者
class Stock {public://添加订阅者对象void attach(Observer *obj) { observerlist_.emplace_back(obj); }//移除订阅者对象void deattach(Observer *obj) { observerlist_.remove(obj); }//向所有订阅股票信息的订阅者发送消息void notify(int price) {for (auto &obj : observerlist_) {obj->update(price);}}//修改价格void set_price(int price) {price_ = price;notify(price);}private:int price_;std::list<Observer *> observerlist_;
};
void test() {Monitor monitor;BillBoard billBoard;Stock stock;//添加订阅者对象stock.attach(&monitor);stock.attach(&billBoard);//修改价格stock.set_price(100);stock.set_price(10000);
}
编译运行
修改
经过上述设计,我们其实已经完成了大部分工作,但是还有一个问题
Stock是发送消息者,但是当订阅消息者每次想要订阅消息时却要Stock将其添加到自己的列表里,这显然是不合理的,这就好比,我想要获取一个公众号的消息,但是必须要先让公众号知道我的存在,然后把我添加进它的列表里才行
对于发送消息者来说,添加的订阅者太多的时候显然是一种负担,而对于订阅者来说,还需要等待发送者将自己添加成功后才能获取到实时消息
因此,我们应该再次修改代码,更换一下主客角色
class Stock;
class Observer {public:explicit Observer(Stock *stock);virtual ~Observer();virtual void update(int) = 0;protected:Stock *stock_;
};Observer::Observer(Stock *stock) : stock_(stock) { stock_->attach(this); }
Observer::~Observer() { stock_->deattach(this); }
如上所示,我们直接修改观察者的代码,为其添加发送方的对象指针,然后调用发送方的方法把自己添加到发送方的列表里,如此:
- 对于订阅消息的人来说,只需要关心发送方是谁即可
- 而对于发送方则不需要关心订阅者是谁
完整代码如下
class Stock;
class Observer {public:explicit Observer(Stock *stock);virtual ~Observer();virtual void update(int) = 0;protected:Stock *stock_;
};class Monitor : public Observer {public:explicit Monitor(Stock *stock) : Observer(stock) {}void printPrice(int price) {std::cout << "monitor >>> " << price << std::endl;}void update(int price) override { printPrice(price); }
};class BillBoard : public Observer {public:explicit BillBoard(Stock *stock) : Observer(stock) {}void displayPrice(int price) {std::cout << "billoard >>> " << price << std::endl;}void update(int price) override { displayPrice(price); }
};class Stock {public:void attach(Observer *obj) { observerlist_.emplace_back(obj); }void deattach(Observer *obj) { observerlist_.remove(obj); }void notify(int price) {for (auto &obj : observerlist_) {obj->update(price);}}void set_price(int price) {price_ = price;notify(price);}private:int price_;std::list<Observer *> observerlist_;
};Observer::Observer(Stock *stock) : stock_(stock) { stock_->attach(this); }
Observer::~Observer() { stock_->deattach(this); }
我们再来看测试代码
void test() {Stock stock;Monitor monitor(&stock);BillBoard billBoard(&stock);stock.set_price(1);stock.set_price(10);stock.set_price(1000);
}
- 对于订阅者来说,我关注的是股票信息,因此将股票类的实例对象添加进去
- 对于发送方来说,我不需要关心谁订阅了我的消息,我只修改股票信息就行
编译运行