设计模式之观察者模式

背景

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

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

  • 一个股票类不时更新股票价格
  • 另外有两个类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);
}
  • 对于订阅者来说,我关注的是股票信息,因此将股票类的实例对象添加进去
  • 对于发送方来说,我不需要关心谁订阅了我的消息,我只修改股票信息就行

编译运行

相关推荐
那年星空21 分钟前
Flutter 设计模式全面解析:抽象工厂
flutter·设计模式·架构
博风1 小时前
设计模式:10、外观模式
设计模式·外观模式
zzzhpzhpzzz8 小时前
设计模式——抽象工厂模式
设计模式·抽象工厂模式
阳光开朗_大男孩儿9 小时前
组合设计模式
c++·设计模式·组合模式·组合设计模式
编程、小哥哥10 小时前
设计模式之代理模式(模拟mybatis-spring中定义DAO接口,使用代理类方式操作数据库原理实现场景)
设计模式·mybatis·代理模式
白茶等风1213811 小时前
Unity 设计模式-状态模式(State Pattern)详解
设计模式·状态模式
好好学习++11 小时前
【HF设计模式】01-策略模式
java·c++·设计模式·策略模式
zzzhpzhpzzz11 小时前
设计模式——状态模式
设计模式·状态模式
oioihoii11 小时前
建造者模式
c++·设计模式·c#·建造者模式