首页 > 编程笔记 > C++笔记 阅读:2

C++观察者模式及其实现(附带实例)

观察者模式是一种一对多的依赖关系,当一个对象的状态发生变化时,其依赖的所有对象都会得到通知并自动更新。

观察者模式通常应用于许多现实世界的场景,如事件处理、消息传递、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 >&param,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℃

相关文章