C++观察者模式及其实现(附带实例)
观察者模式是一种一对多的依赖关系,当一个对象的状态发生变化时,其依赖的所有对象都会得到通知并自动更新。
观察者模式通常应用于许多现实世界的场景,如事件处理、消息传递、GUI开发等。在软件开发中,观察者模式有着广泛的应用,例如在 MVC(Model-View-Controller)模式中,视图(View)通常充当观察者角色,模型(Model)则是主题,视图监听模型的变化以更新自身的状态。
观察者模式的优点是解耦了目标和观察者,使它们可以独立发展,并且可以灵活地增加、删除观察者对象。同时,观察者模式符合开闭原则,可以在不修改目标对象的情况下增加新的观察者。
观察者模式的缺点主要是,首先观察者模式可能引起性能问题,特别是当观察者较多或者更新操作较复杂时;其次,观察者模式可能导致循环引用,需要避免。

图 1 传统观察者模式UML简图
目标是被观察者的对象,它维护一组观察者对象,并提供方法,用于添加、删除和通知观察者。观察者是定义了一个更新接口的对象,它能够接收目标的通知并相应地进行更新操作。具体观察者是观察者接口的实现类,它实现了更新接口,并定义了具体的更新操作。
为了将不同数量的数据类型打包成一个 std::vector<variant> ,模块中使用模板递归的方式进行打包处理,如下图所示。

图 2 C++11模板观察者模式UML简图
观察者 observer 是一个包含纯虚函数的基类,在实际的观察者中需要实现 update 方法来响应目标数据发生变化后的通知。在 subject 中维护了一个观察者的对象表,当 subject 发生变化后会发出通知。
FOR__ 是一个运行期的元函数,用来将可变的参数转换为 std::vector<variant> 数据,代码如下:
FOR__ 通过递归展开的方式,每次展开处理一种数据类型。将 std::tuple 封装的数据逐个封装成 std::vector<variant>,代码如下:
下面展开终止条件,最后一个展开的 extract() 函数为了兼容整个调用的逻辑关系,虽然定义了函数,但是实际上不需要使用,代码如下:
函数 addObserver() 用来添加观察者对象。removeObserver() 函数用来移除观察者对象,代码如下:
模板函数 notifyObservers() 用来发送变化通知,这种方法使用可变参模板实现以增加通用性。内部通过构造万能数据类型 wheels::variant 来存储实际的数据内容,并将所有的数据构造成向量对象作为参数进行传递,代码如下:
在类 Display 中需要实现虚函数 update(),用来接收事件通知。在 TemperatureSensor 类中,发生事件时需要调用基类中的 notifyObservers() 函数发出通知,代码如下:
在 main() 函数中定义一个被观察对象 sensor 和两个观察者 display1、display2,sensor 类首先调用 addObserver() 添加两个观察者,然后调用 updateTemperature() 函数发送通知,代码如下:
观察者模式通常应用于许多现实世界的场景,如事件处理、消息传递、GUI开发等。在软件开发中,观察者模式有着广泛的应用,例如在 MVC(Model-View-Controller)模式中,视图(View)通常充当观察者角色,模型(Model)则是主题,视图监听模型的变化以更新自身的状态。
观察者模式的优点是解耦了目标和观察者,使它们可以独立发展,并且可以灵活地增加、删除观察者对象。同时,观察者模式符合开闭原则,可以在不修改目标对象的情况下增加新的观察者。
观察者模式的缺点主要是,首先观察者模式可能引起性能问题,特别是当观察者较多或者更新操作较复杂时;其次,观察者模式可能导致循环引用,需要避免。
传统观察者模式
传统观察者模式一般涉及 3 个角色:目标(Subject)、观察者(Observer)和具体观察者(Concrete Observer),如下图所示。
图 1 传统观察者模式UML简图
目标是被观察者的对象,它维护一组观察者对象,并提供方法,用于添加、删除和通知观察者。观察者是定义了一个更新接口的对象,它能够接收目标的通知并相应地进行更新操作。具体观察者是观察者接口的实现类,它实现了更新接口,并定义了具体的更新操作。
C++11元编程下的结构设计
本节的观察者模式模板类通过 variant 万能数据类型存储数据内容,用于通知消息传递。由于需要考虑实际使用的数据数量和类型都会有不同的变化,所以使用可变参的函数模板,用来触发通知操作,将多种不同的参数组包成 std::vector<variant> 的方式来传递参数。为了将不同数量的数据类型打包成一个 std::vector<variant> ,模块中使用模板递归的方式进行打包处理,如下图所示。

图 2 C++11模板观察者模式UML简图
C++观察者模式实现和解析
代码主要分成 3 部分,即观察者 observer、观察目标 subject 和打包器 FOR__。观察者 observer 是一个包含纯虚函数的基类,在实际的观察者中需要实现 update 方法来响应目标数据发生变化后的通知。在 subject 中维护了一个观察者的对象表,当 subject 发生变化后会发出通知。
FOR__ 是一个运行期的元函数,用来将可变的参数转换为 std::vector<variant> 数据,代码如下:
//designM/observer.hpp class observer{ public: virtual~observer(){} virtual void update(const std::vector< wheels::variant >&data) = 0; //内部使用 virtual bool needRelease(){return false;} };
FOR__ 通过递归展开的方式,每次展开处理一种数据类型。将 std::tuple 封装的数据逐个封装成 std::vector<variant>,代码如下:
//designM/observer.hpp template< int N,typename tupleType > struct FOR__ { static void extract(std::vector<wheels::variant >¶m,const tupleType&t) { param[N] = wheels::variant::make(std::get< N >(t)); FOR__< N - 1,tupleType >::extract(param,t); } };
下面展开终止条件,最后一个展开的 extract() 函数为了兼容整个调用的逻辑关系,虽然定义了函数,但是实际上不需要使用,代码如下:
template<typename tupleType> struct FOR__<0, tupleType> { static void extract(std::vector<wheels::variant>& param, const tupleType& t) { (void)param; (void)t; } }; class subject { public: using obsvFunc_t = std::function<void(const std::vector<wheels::variant>&)>; protected: // 管理以函数对象添加的 observer 对象 class observer__ : public observer { private: obsvFunc_t m_func__; public: explicit observer__(obsvFunc_t func) : m_func__(func) {} virtual ~observer__() {} virtual void update(const std::vector<wheels::variant>& data) final { m_func__(data); } virtual bool needRelease() final { return true; } }; public: subject() {} virtual ~subject() {} };
函数 addObserver() 用来添加观察者对象。removeObserver() 函数用来移除观察者对象,代码如下:
// designM/observer.hpp template<typename realType> void addObserver(realType* obsv) { std::unique_lock<std::mutex> lck(m_mutex__); m_observers__.push_back(obsv); } observer* addObserver(obsvFunc_t func) { observer* ret = nullptr; ret = new observer__(func); { std::unique_lock<std::mutex> lck(m_mutex__); m_observers__.push_back(ret); } return ret; } void removeObserver(observer* obsv) { std::unique_lock<std::mutex> lck(m_mutex__); if (m_observers__.size() == 0) return; auto it = std::find(m_observers__.begin(), m_observers__.end(), obsv); if (it != m_observers__.end()) { if ((*it)->needRelease()) { delete *it; } m_observers__.erase(it); } }
模板函数 notifyObservers() 用来发送变化通知,这种方法使用可变参模板实现以增加通用性。内部通过构造万能数据类型 wheels::variant 来存储实际的数据内容,并将所有的数据构造成向量对象作为参数进行传递,代码如下:
// designM/observer.hpp template<typename ...Args> void notifyObservers(Args&&... args) { // 构造 variant 数组 if (sizeof...(args) > 0) { // 构造 tuple 以方便后续展开 auto t = std::make_tuple(std::forward<Args>(args)...); // 定义要传递的参数 param std::vector<wheels::variant> param(sizeof...(args)); // 使用 FoR 模板展开,把 tuple 中的数据提取到 vector 中 FoR__<sizeof...(args) - 1, decltype(t)>::extract(param, t); // 加锁并通知所有观察者 std::lock_guard<std::mutex> lck(m_mutex__); for (auto obsv : m_observers__) { obsv->update(param); } } else { // 当没有参数时使用空的 vector std::vector<wheels::variant> param; std::lock_guard<std::mutex> lck(m_mutex__); for (auto obsv : m_observers__) { obsv->update(param); } } } private: std::mutex m_mutex__; std::vector<observer*> m_observers__; };
C++观察者模式应用示例
下面的示例模拟从温度测量到内容显示的过程。类 TemperatureSensor 是温度传感器类,作为被观察对象;类 Display 和类 DisplaySec 是显示器类,作为观察者。在类 Display 中需要实现虚函数 update(),用来接收事件通知。在 TemperatureSensor 类中,发生事件时需要调用基类中的 notifyObservers() 函数发出通知,代码如下:
//observer.cpp #include <iostream> #include <string> #include "designM/observer.hpp" using namespace wheels; using namespace dm; class TemperatureSensor : public subject { public: void updateTemperature(int temp) { notifyObservers(temp); } }; class Display : public observer { public: Display() {} virtual void update(const std::vector<wheels::variant>& data) override { if (data.size() > 0) { int temperature = data[0].get<int>(); std::cout << "Display 当前温度为 " << temperature << "℃" << std::endl; } } private: TemperatureSensor m_sensor; }; class DisplaySec : public observer { public: DisplaySec() {} virtual void update(const std::vector<wheels::variant>& data) override { if (data.size() > 0) { int temperature = data[0].get<int>(); std::cout << "DisplaySec 当前温度为 " << temperature << "℃" << std::endl; } } private: TemperatureSensor m_sensor; };
在 main() 函数中定义一个被观察对象 sensor 和两个观察者 display1、display2,sensor 类首先调用 addObserver() 添加两个观察者,然后调用 updateTemperature() 函数发送通知,代码如下:
int main() { //定义两个不同的观察者 Display display1; DisplaySec display2; TemperatureSensor sensor; //添加观察者 sensor.addObserver(&display1); sensor.addObserver(&display2); //发出通知 sensor.updateTemperature(25); sensor.updateTemperature(28); return 0; }上述示例的运行结果如下:
Display当前温度为25℃
DisplaySec当前温度为25℃
Display当前温度为28℃
DisplaySec当前温度为28℃