设计模式之观察者模式

背景

假如我们现在有这样一个场景:市场上的股票价格不定时变化,而后台监控者和广告想要实时获取股票信息,我们应该怎么做?

显然在这个场景里,我们有这样一个设计

  • 一个股票类不时更新股票价格
  • 另外有两个类Monitor和BillBoard实时接收股票价格的更新并显示出来

基于以上想法,我们先写出一版代码出来

cpp 复制代码
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;
    }
};
cpp 复制代码
class Stock {
 public:
    //实时更新价格,并价格信息反馈给Monitor和BillBoard
    void 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_;
};
cpp 复制代码
void test() {
    Stock stock;
    stock.set_price(20);
}

上述代码显然是可以运行的,当Stock修改价格信息时,Monitor和BillBoard可以接受到Stock发送给它的价格信息

但上述代码设计的缺陷很明显

  • Stock类和其余两个类之间的耦合度过高,假如我们还有其他的类也想要实时获取股票信息,那么每次就都得修改Stock类的代码
  • 在实际场景里Stock不可能知道想要获取股票信息的类的方法具体是什么,也就是说printPrice方法和displayPrice对于Stock并不是事先知道的

观察者模式

初步设计

基于上述第二个缺陷,我们可以设计一个虚函数,将Monitor和BillBoard的打印股票信息的方法抽象成一个统计的接口,如下所示

cpp 复制代码
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就知道调用哪个方法来发送股票信息了

cpp 复制代码
class Stock {
 public:
    void set_price(int price) {
        price_ = price;
        observer->update(price);
    }

 private:
    int price_;
    Observer * observer;
};

为了能够一次性通知所有订阅者,我们可以使用一个容器保存所有的订阅者,然后遍历发送消息,像这样

cpp 复制代码
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_;
};

当然,想要将消息发送出去,保存消息的容器当然要有订阅者对象才行,因此我们需要设计两个方法用于添加订阅者和撤销订阅者

cpp 复制代码
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_;
};
cpp 复制代码
void test() {
    Monitor monitor;
    BillBoard billBoard;

    Stock stock;
    //添加订阅者对象
    stock.attach(&monitor);
    stock.attach(&billBoard);
    
    //修改价格
    stock.set_price(100);
    stock.set_price(10000);
}

编译运行

修改

经过上述设计,我们其实已经完成了大部分工作,但是还有一个问题

Stock是发送消息者,但是当订阅消息者每次想要订阅消息时却要Stock将其添加到自己的列表里,这显然是不合理的,这就好比,我想要获取一个公众号的消息,但是必须要先让公众号知道我的存在,然后把我添加进它的列表里才行

对于发送消息者来说,添加的订阅者太多的时候显然是一种负担,而对于订阅者来说,还需要等待发送者将自己添加成功后才能获取到实时消息

因此,我们应该再次修改代码,更换一下主客角色

cpp 复制代码
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); }

如上所示,我们直接修改观察者的代码,为其添加发送方的对象指针,然后调用发送方的方法把自己添加到发送方的列表里,如此:

  • 对于订阅消息的人来说,只需要关心发送方是谁即可
  • 而对于发送方则不需要关心订阅者是谁

完整代码如下

cpp 复制代码
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); }

我们再来看测试代码

cpp 复制代码
void test() {
    Stock stock;
    Monitor monitor(&stock);
    BillBoard billBoard(&stock);

    stock.set_price(1);
    stock.set_price(10);
    stock.set_price(1000);
}
  • 对于订阅者来说,我关注的是股票信息,因此将股票类的实例对象添加进去
  • 对于发送方来说,我不需要关心谁订阅了我的消息,我只修改股票信息就行

编译运行

相关推荐
高晶15 小时前
CANopen 在 BMS 系统中的 C 语言实战指南
设计模式·程序员
博风16 小时前
13、设计模式应用举例:工厂方法模式:创建药品对象
设计模式·工厂方法模式
代钦塔拉18 小时前
【C++/Qt 进阶】设计模式系列(5):适配器模式——给“不兼容的接口“贴一层翻译膜
c++·qt·设计模式
bigbread1 天前
策略模式 + 工厂方法模式在项目中的实战应用
设计模式
高晶1 天前
C 语言实现 BMS 系统中 SPI 通信协议实战指南
设计模式·程序员
代钦塔拉1 天前
【C++/Qt 进阶】设计模式系列(2):工厂模式——从游戏角色到披萨店
c++·算法·设计模式
高晶1 天前
2A3 单端甲类功放从零制作全攻略:电路设计、焊接调试到煲机维护一站式指南
前端·设计模式
代钦塔拉2 天前
【C++/Qt 进阶】设计模式系列(4):原型模式——用“克隆“代替“重新构造“
c++·qt·设计模式
博风2 天前
11、设计模式应用举例:迭代器模式:使用多个集合存储对象
设计模式·迭代器模式
日和雫2 天前
面向对象与面向过程,谁才是“银弹”?
设计模式