C++屌屌的观察者模式-同步回调和异步回调
目录
一、概述
说起观察者模式,也是比较简单的一种模式了,稍微工作有1年经验的同学,写起来都是666...
想看观察者模式的说明可以直接上这个地址去看。
本篇文章其实就是一个简单的观察者模式,只是使用了模板的方式,把我们的回调接口进行了参数化,这样有什么好处呢?
好处当然是大大的有了。 平时我们在不同业务逻辑之间写观察者模式呢,都得写好多个,大家有没有发现,所有的被观察者subject其实很多操作都是一样的。
本篇我们带来两种观察者模式:同步观察者和异步观察者
1、同步观察者
顾名思义,同步观察者其实就是不管是谁,触发了subject的update操作,该操作都是同步进行的,他会调用所有的观察者(observer)的onupdate接口,来通知observer处理改变操作。
如效果展示图中的第一个单次拉取
页签,当我们点击拉取按钮时,就相当于触发了一次subject对象的update操作
2、异步观察者
异步观察者模式上和同步观察者基本一样,只是在事件处理上有稍微不同
- 执行update操作是由subject自己去完成的
- 调用observer的onupdate回调接口时,处于工作线程中
- subject所有的请求操作都是在工作现场中进行
如效果图所示,定时拉取
观察者模式,subject启动了一个后台线程,3秒钟拉取一次数据,并回调到界面
二、效果展示
如下图所示,是一个简单的观察者模式事例。
单次拉取
:演示了同步观察者模式
定时拉取
:演示了异步观察者模式
工程结构如图所示,这里只把头文件的目录展示出来了。
实现文件的目录和头文件类似,为了截图方便所以做了隐藏操作。
header files目录下有2个虚拟文件夹,分别就是对单次拉取
和定时拉取
功能的实践
下面我们就来正式开始讲解这个屌屌的观察者模式
三、同步观察者
1、首先就是定义一堆接口和回调参数
struct dataitem { std::string strid; std::string strname; }; typedef iupdate1<dataitem> isignalobserver; //单次回调 struct isignal : public subjectbase<isignalobserver> { virtual void requestdata() = 0; };
2、业务观察者
这里我定义了一个signalresponse业务观察者,也就是我们在开发工程中的实际功能类。
class signalresponse : public isignal { public: signalresponse(); ~signalresponse(); public: virtual void requestdata() override; private: };
*3、获取观察者指针**
通过一个门面接口获取观察者指针
- 调用isignal的attach接口,就可以把自己添加到观察者列表。
- 调用isignal的requestdata接口,就可以拉取数据。
- 调用isignal的detach接口,就可以把自己从观察者列表中移除。
isignal * getsignalcommon();
4、ui界面
接下来就是写一个ui界面啦,当我们通过上一步调用拉取数据接口后,我们的ui上相应的onupdate接口就会被回调
class signalwidget : public qwidget, public isignalobserver { q_object public: signalwidget(qwidget * parent = 0); ~signalwidget(); protected: virtual void onupdate(const dataitem &) override; private slots: void on_pushbutton_clicked(); private: ui::signalwidget *ui; };
通过以上四步,就可以很方便的实现一个现在业务中的观察者,是不是很简单呢,编写过程中,需要完成这几个地方
- 需要定义我们回调函数的参数结构
- 需要实例化一个被观察者接口类
- 实例化一个业务观察者
- 做一个ui界面,并集成第二步实例化的被观察者的模板参数(接口类)
注意看这里的isignalobserver,是不是很眼熟,其实他就是我们的模板被观察者subjectbase的模板参数。
讲到这里,大家是不是都很关心这个模板观察者到底是何方神圣,居然这么叼。那么接下来就是模板subjectbase出场啦。。。
下面我直接给出代码,学过c++的同学阅读起来应该都不难。
觉着难了就多读几遍
template <typename t> struct isubject { virtual void attach(t * pobserver) = 0; virtual void detach(t * pobserver) = 0; }; template <typename p> struct iupdate1 { virtual void onupdate(const p& data) = 0; }; template <typename p1, typename p2> struct iupdate2 { virtual void onupdate2(const p1 & p1, const p2 & p2) = 0; }; template <typename p> struct iupdate1_p { virtual void onupdate(const p * data) = 0; }; template <typename t> struct subjectbase { public: virtual void attach(t * pobserver) { std::lock_guard<std::mutex> lg(m_mutex); #ifdef _debug if (m_observers.end() != std::find(m_observers.begin(), m_observers.end(), pobserver)) { assert(false); } #endif // _debug m_observers.push_back(pobserver); } virtual void detach(t * pobserver) { std::lock_guard<std::mutex> lg(m_mutex); auto it = std::find(m_observers.begin(), m_observers.end(), pobserver); if (it != m_observers.end()) { m_observers.erase(it); } else { assert(false); } } //protected: template <typename p> void updateimpl(const p & data) { std::lock_guard<mutex> lg(m_mutex); for (t * observer : m_observers) { observer->onupdate(data); } } template <typename p> void updateimpl(p & data) { std::lock_guard<std::mutex> lg(m_mutex); for (t* observer : m_observers) { observer->onupdate(data); } } template <typename p1, typename p2> void updateimpl(const p1& p1, const p2& p2) { std::lock_guard<mutex> lg(m_mutex); for (t* observer : m_observers) { observer->onupdate2(p1, p2); } } template <typename p1, typename p2> void updateimpl(p1& p1, p2& p2) { std::lock_guard<mutex> lg(m_mutex); for (t* observer : m_observers) { observer->onupdate2(p1, p2); } } template <typename p> void updateimpl(const p * data) { std::lock_guard<mutex> lg(m_mutex); for (t * observer : m_observers) { observer->onupdate(data); } } template <typename p> void updateimpl(p * data) { std::lock_guard<mutex> lg(m_mutex); for (t* observer : m_observers) { observer->onupdate(data); } } protected: std::mutex m_mutex; std::list<t *> m_observers; };
四、异步观察者
异步观察者的实现和同步观察者的结构基本一样,都是使用同样的套路,唯一有区别的地方就是,异步观察者所有的逻辑处理操作都是在工作线程中的。
由于itimersubject和subjectbase很多接口都是一样的,因此我这里就只把差异的部分贴出来。
1、线程
itimersubject对象在构造时,就启动了一个线程,然后在线程中定时执行timernotify函数
itimersubject() { m_thread = std::thread(std::bind(&itimersubject::timernotify, this)); } virtual ~itimersubject() { m_thread.join(); }
再来看下定时处理任务这个函数,这个函数本身是用boost的库实现我的,我改成c++11的模式的,新城退出这块有些问题,我没有处理,这个也不是本篇文章的核心要讲解的东西。
怎么优雅的退出std::thread,这个从网上查下资料吧,我能想到的也就是加一个标识,然后子线程去判断。如果大家有更好的办法的话可以私信我,或者在底部留言。
void timernotify() { for (;;) { //std::this_thread::interruption_point(); bool bnotify = false; { std::lock_guard<std::mutex> lg(m_mutex); bnotify = m_sleeping_observers.size() < m_observers.size() ? true : false; } if (bnotify) { ontimernotify(); } //std::this_thread::interruption_point(); std::chrono::milliseconds timespan(gettimerinterval() * 1000); // or whatever std::this_thread::sleep_for(timespan); } }
2、定义一堆接口和回调参数
struct timerdataitem { std::string strid; std::string strname; }; typedef iupdate1<timerdataitem> itimerobserver; //定时回调 struct itimer : public itimersubject<itimerobserver, std::string, timerdataitem>{};
3、业务观察者
这里我定义了一个timerresponse业务观察者,也就是我们在开发工程中的实际功能类。
class timerresponse : public itimer { public: timerresponse(); ~timerresponse(); protected: virtual void onnotify() override; private: };
timerresponse::onnotify()这个接口的实现就像这样,这里需要注意的一点是,这个函数的执行位于工作线程中,也就意味着ui界面的回调函数也在工作线程中,操作ui界面时,一定需要抛事件到ui线程中。
void timerresponse::onnotify() { static int id = 0; static std::string name = "miki"; id += 1; timerdataitem item; std::stringstream ss; ss << "timer" << id; item.strid = ss.str(); item.strname = name; updateimpl(item); }
onnotify会定时被调用,然后去更新ui上的内容。
4、获取观察者指针
通过一个门面接口获取观察者指针,调用itimer的attach接口把自己添加到观察者列表,然后就可以定时获取到数据,反之也能把自己从观察者列表中移除,并停止接收到数据。
itimer * gettimercommon();
5、ui界面
定时回调功能测试界面
- on_pushbutton_clicked槽函数只是为了把当前线程唤醒,并定时回调
- onupdate属于定时回调接口
class timerwidget : public qwidget, public itimerobserver { q_object public: timerwidget(qwidget *parent = 0); ~timerwidget(); protected: virtual void onupdate(const timerdataitem &) override; private slots: void on_pushbutton_clicked(); signals: void rerfushdata(timerdataitem); private: ui::timerwidget *ui; };
上边也强调过了,onupdate的执行是在工作线程中的,因此实现的时候,如果涉及到访问ui界面,一定要注意切换线程
void timerwidget::onupdate(const timerdataitem & item) { //注意这里的定时回调都在工作线程中 需要切换到主线程 emit rerfushdata(item); }
以上讲解就是我们观察者的实现了,如果有疑问欢迎提出
五、相关文章
如果您觉得文章不错,不妨给个打赏,写作不易,感谢各位的支持。您的支持是我最大的动力,谢谢!!!
很重要--转载声明
本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者: or twowords
如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。