原文连接:C++屌屌的观察者模式-同步回调和异步回调异步
提及观察者模式,也是比较简单的一种模式了,稍微工做有1年经验的同窗,写起来都是666...ide
想看观察者模式的说明能够直接上菜鸟教程|观察者模式这个地址去看。函数
本篇文章其实就是一个简单的观察者模式,只是使用了模板的方式,把咱们的回调接口进行了参数化,这样有什么好处呢?测试
好处固然是大大的有了。 平时咱们在不一样业务逻辑之间写观察者模式呢,都得写好多个,你们有没有发现,全部的被观察者Subject其实不少操做都是同样的。ui
本篇咱们带来两种观察者模式:同步观察者和异步观察者this
顾名思义,同步观察者其实就是不论是谁,触发了Subject的Update操做,该操做都是同步进行的,他会调用全部的观察者(Observer)的OnUpdate接口,来通知Observer处理改变操做。spa
如效果展现图中的第一个单次拉取
页签,当咱们点击拉取按钮时,就至关于触发了一次Subject对象的Update操做线程
异步观察者模式上和同步观察者基本同样,只是在事件处理上有稍微不一样指针
如效果图所示,定时拉取
观察者模式,Subject启动了一个后台线程,3秒钟拉取一次数据,并回调到界面
以下图所示,是一个简单的观察者模式事例。
单次拉取
:演示了同步观察者模式
定时拉取
:演示了异步观察者模式
工程结构如图所示,这里只把头文件的目录展现出来了。
实现文件的目录和头文件相似,为了截图方便因此作了隐藏操做。
Header Files目录下有2个虚拟文件夹,分别就是对单次拉取
和定时拉取
功能的实践
下面咱们就来正式开始讲解这个屌屌的观察者模式
一、首先就是定义一堆接口和回调参数
struct DataItem { std::string strID; std::string strName; }; typedef IUpdate1<DataItem> ISignalObserver; //单次回调 struct ISignal : public SubjectBase<ISignalObserver> { virtual void RequestData() = 0; };
二、业务观察者
这里我定义了一个SignalResponse业务观察者,也就是咱们在开发工程中的实际功能类。
class SignalResponse : public ISignal { public: SignalResponse(); ~SignalResponse(); public: virtual void RequestData() override; private: };
*三、获取观察者指针**
经过一个门面接口获取观察者指针
ISignal * GetSignalCommon();
四、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; };
经过以上四步,就能够很方便的实现一个如今业务中的观察者,是否是很简单呢,编写过程当中,须要完成这几个地方
注意看这里的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不少接口都是同样的,所以我这里就只把差别的部分贴出来。
一、线程
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); } }
二、定义一堆接口和回调参数
struct TimerDataItem { std::string strID; std::string strName; }; typedef IUpdate1<TimerDataItem> ITimerObserver; //定时回调 struct ITimer : public ITimerSubject<ITimerObserver, std::string, TimerDataItem>{};
三、业务观察者
这里我定义了一个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上的内容。
四、获取观察者指针
经过一个门面接口获取观察者指针,调用ITimer的Attach接口把本身添加到观察者列表,而后就能够定时获取到数据,反之也能把本身从观察者列表中移除,并中止接收到数据。
ITimer * GetTimerCommon();
五、UI界面
定时回调功能测试界面
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); }
以上讲解就是咱们观察者的实现了,若是有疑问欢迎提出
![]() |
![]() |
很重要--转载声明