设计模式:2. 观察者模式

笔者目前正在阅读《Head First设计模式》,首先对该书发表我的个人观点,非常有趣生动的一本书,在阅读这本书之前,我一直对设计模式抱有畏惧的心理,阅读该书之后,才发现设计模式可以这么通俗易懂,推荐各位去阅读原书。为加深知识印象,对书中内容进行梳理总结,书中的案例均由Java实现,而笔者本人目前主要使用C++,因此该文章通过C++来描述案例。由于本人水平有限,表达会有欠佳处,若要深入理解设计模式,还是推荐读者能够阅读原书。

这次,我们收到了一个气象站的需求,气象站会将测量到的温度、湿度与气压等数据告诉我们,我们需要设计一个系统将气象情况在不同的设备上进行显示(显示的方式,显示需要的数据可能都不相同)。目前,我们通过一个WeatherData类来接收数据信息,并将他们显示到设备上。接下来我们设想了第一个版本。

class WeatherData
{
public:
  // 此处省略赋值显示设备的相关代码

  void setMeasurements(float temperature, float humidity, float pressure)
  {
    // 假设当测量到的数据发生变化时,会自动调用该函数。
    m_temperature = temperature;
    m_humidity = humidity;
    m_pressure = pressure;
    measurementsChanged();
  }
  void measurementsChanged()
  {
    // 更新设备中的显示内容
    m_device1->update(m_temperature, m_humidity, m_pressure);
    m_device2->update(m_temperature, m_humidity, m_pressure);
    m_device3->update(m_temperature, m_humidity, m_pressure);
  }
  
private:
  float m_temperature;  // 温度
  float m_humidity;  // 湿度
  float m_pressure;  // 气压

  DisplayDevice* m_device1;
  DisplayDevice* m_device2;
  DisplayDevice* m_device3;
};

可以看到,持有状态的这个对象(WeatherData)和设备耦合在了一起。如果我们需要增加一个显示设备呢,又或则某一个设备不再想要这些信息了,这时候就必须修改代码。这显然不是一个好的设计方式。我们要尽可能遵循一个设计原则:为交互对象之间的松耦合设计而努力
既然他们耦合到一起了,那么我们如何想办法解耦呢?状态对象不需要知道有哪些类依赖了它,我们自然的想到了接口。如果有一个接口,它声明了update()函数,所有的设备都继承这个接口,这样WeatherData就不必知道有哪种设备需要update(),它只需要调用接口的update()函数,通过多态,各种类型的设备自然能够通过自己的方式展示数据。
之后,我们又联想到报纸和杂志的订阅,如果我们浏览报纸时,只需要向某家报社订阅报纸,只要他们有新的报纸出版,就会给你送来。当你不再想看时,只需要取消订阅。
在我们的例子中,WeatherData就是提供报纸的报社,各个设备需要订阅WeatherData中的数据。
我们先来实现刚刚提到的接口,所有实现这个接口的类就是需要订阅报纸的客户,也就是观察者

class Observer
{
public:
  virtual void update(float temperature, float humidity, float pressure) = 0;
};

不同的显示设备都会实现这个接口

class Phone: public Observer
{
public:
  void update(float temperature, float humidity, float pressure)
  {
    std::cout << "在手机上显示天气信息" << std::endl;
  }
};
class Computer: public Observer
{
public:
  void update(float temperature, float humidity, float pressure)
  {
    std::cout << "在电脑上显示天气信息" << std::endl;
  }
};

而持有状态的类(需要被观察者观察的,如WeatherData),我们称之为主题。在一个系统中,可能会有多种主题,我们将主题设计成接口,而WeatherData就是一个具体的主题。

class Subject
{
public:
  virtual void registerObserver(Observer* o) = 0;  // 订阅主题
  virtual void removeObserver(Observer* o) = 0;  // 取消订阅
  virtual void notifyObservers() = 0;  // 通知订阅者
};
class WeatherData: public Subject
{
public:
  void registerObserver(Observer* o) override
  {
    m_observers.push_back(o);
  }
  void removeObserver(Observer* o) override
  {
    auto it = std::find(m_observers.begin(), m_observers.end(), o);
    if (it != m_observers.end())
    {
      m_observers.erase(it);
    }
  }
  void notifyObservers() override
  {
    for(const Observer* observer: m_observers)
    {
      observer->update(m_temperature, m_humidity, m_pressure);
    }
  }
  void setMeasurements(float temperature, float humidity, float pressure)
  {
    // 假设当测量到的数据发生变化时,会自动调用该函数。
    m_temperature = temperature;
    m_humidity = humidity;
    m_pressure = pressure;
    notifyObservers();
  }
private:
  std::vector<Observer*> m_observers;
  float m_temperature;  // 温度
  float m_humidity;  // 湿度
  float m_pressure;  // 气压
};

于是乎,我们实现了观察者模式!
上方例子中,数据是通过主题推送给观察者的。有时候可能观察者不太想要这么多内容,那么,可以让主题提供数据的get函数,在订阅主题时,将主题对象作为成员对象保存在观察者中,当观察者被通知时,可以直接通过这个观察者对象的get函数,自己拉取数据。由于篇幅有限,这里不做演示了,更加详细的内容,还是推荐去阅读原书。
最后,给出观察者模式的定义:在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新

posted @ 2026-06-14 17:01  初五二十一  阅读(5)  评论(0)    收藏  举报