欢迎光临
我们一直在努力

深入解析观察者模式:一个C++模板实现的通用观察者库

深入解析观察者模式:一个C++模板实现的通用观察者库

1. 观察者模式简介

在软件开发中,我们经常需要处理对象之间的状态依赖关系——当一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动更新。这就是观察者模式(Observer Pattern) 的典型应用场景。比如,在图形界面中,当用户修改了模型数据,所有相关的视图都需要刷新;在股票系统中,当股价变动时,所有订阅该股票的客户端都应收到通知。

观察者模式定义了对象之间的一对多依赖关系,让多个观察者对象同时监听某一个主题对象。当主题状态发生变化时,它会通知所有观察者,使它们能够自动更新。这种模式极大地降低了主题与观察者之间的耦合度。

今天我们要介绍的是一个基于C++模板实现的通用观察者类库。它不仅支持传统的观察者对象,还支持函数式观察者(通过std::function),并且可以通过编译开关选择同步通知或异步通知(利用线程池)。无论是简单的回调还是高性能的异步处理,它都能轻松应对。

2. 代码设计解析

2.1 整体设计思路

这个观察者框架的核心思想是:主题(Subject)维护一个观察者列表,当状态变化时遍历列表通知所有观察者。它的设计亮点在于:

  • 统一的观察者接口:observer 抽象类定义了update方法,所有观察者都必须实现它。
  • 灵活的观察者添加:支持传入观察者对象指针,也支持传入std::function函数对象(框架内部会自动包装成观察者对象)。
  • 参数类型擦除:使用自定义的wheels::variant类型擦除技术,使得通知可以传递任意数量和类型的参数。
  • 同步/异步可选:通过宏OBSERVER_ENABLE_AYSNC控制是否启用异步通知,异步模式下使用线程池执行观察者的更新操作,避免阻塞主题线程。

为了实现这些功能,它设计了几个关键组件:

  • observer抽象类:观察者接口,包含update纯虚函数和needRelease虚函数(用于标记是否需要内部释放)。
  • observer__内部类:继承自observer,用于包装函数对象,使函数也可以作为观察者。
  • subject类:主题类,管理观察者列表,提供添加、移除、通知等方法。
  • FOR__模板结构:用于将可变参数打包成tuple,再递归提取到vector<variant>中,实现参数的类型擦除。

2.2 观察者接口:observer

class observer {


public:
virtual ~observer(){

}
virtual void update( const std::vector< wheels::variant> & data ) = 0;
virtual bool needRelease(){

return false; }
};

  • update方法接受一个vector<variant>,包含了所有通知参数。观察者子类需要从这个容器中解析出具体类型的数据。
  • needRelease方法用于标识该观察者对象是否由框架内部创建(如包装函数对象的观察者),如果是,则主题在移除时需负责delete它。

2.3 参数提取助手:FOR__

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 );
}
};

template< typename tupleType >
struct FOR__<0, tupleType >{


static void extract( std::vector< wheels::variant >& param , const tupleType& t ){


param[ 0 ] = wheels::variant::make(std::get< 0 >( t ) );
}
};

这是一个递归模板,用于将tuple中的每个元素转换成variant并存入vector。当通知参数数量为N时,FOR__<N-1>递归展开,将每个位置的元素逐一提取并赋值给vector的对应位置。这个设计使得任意类型、任意数量的参数都能被统一为vector<variant>,实现了类型擦除。

2.4 主题类:subject

class subject {


public:
using obsvFunc_t = std::function< void ( const std::vector<wheels::variant>& ) >;

// 内部包装类
class observer__ : public observer {

... };

subject();
~subject();

// 添加观察者(对象指针)
template< typename realType >
void addObserver( realType* obsv );

// 添加观察者(函数对象)
observer* addObserver( obsvFunc_t func );

// 移除观察者
void removeObserver( observer* obsv );

// 同步通知
template< typename ...Args >
void notifyObservers( Args&&... args);

#if OBSERVER_ENABLE_AYSNC == 1
// 异步通知(需要线程池支持)
template< typename ...Args >
void notifyObserversAsync( Args&&... args);
#endif

private:
std::mutex m_mutex__;
std::vector< observer* > m_observers__;
#if OBSERVER_ENABLE_AYSNC == 1
std::shared_ptr< threadPool > pt_thd_pool__;
#endif
};

  • 构造函数:如果启用了异步模式,会创建并启动线程池,线程数可由宏OBSERVER_THD_COUNT设置(默认4)。
  • 析构函数:停止线程池(异步模式下),并释放所有标记为needRelease的观察者对象。
  • 添加观察者:两个重载,一个接受任意类型的观察者对象指针(需要继承自observer),另一个接受std::function,内部会创建observer__包装对象并返回其指针。
  • 移除观察者:从列表中移除指定观察者,如果该观察者needRelease()为真,则delete它。
  • 同步通知:将可变参数通过FOR__转换为vector<variant>,然后遍历观察者列表,依次调用update。
  • 异步通知:与同步类似,但将每个观察者的update调用封装成任务,提交给线程池执行。注意在异步模式下,update的执行发生在线程池的工作线程中,因此需要确保线程安全。

2.5 异步模式与线程池

当定义OBSERVER_ENABLE_AYSNC=1时,代码会包含线程池相关逻辑。线程池threadPool是一个假设存在的组件,它提供了enqueue方法将任务加入队列,并由工作线程执行。这种设计将通知操作异步化,主题线程无需等待观察者处理完毕,大大提高了响应速度,特别适合观察者处理耗时操作的场景。

3. 使用示例

假设我们有一个股票市场系统,当股票价格变动时,需要通知多个显示终端和数据分析模块。

3.1 定义观察者类

#include <iostream>
#include <string>
#include \”observer.hpp\” // 假设观察者框架头文件为 observer.hpp

// 终端显示观察者
class Display : public wheels::dm::observer {


std::string name_;
public:
Display(const std::string& name) : name_(name) {

}

void update(const std::vector<wheels::variant>& data) override {


// 解析参数:假设第一个参数是股票代码(string),第二个是价格(double)
if (data.size() >=

赞(0)
未经允许不得转载:171主机测评 » 深入解析观察者模式:一个C++模板实现的通用观察者库
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址