原文链接:C++屌屌的观察者模式-同步回调和异步回调

一、概述

说起观察者模式,也是比较简单的一种模式了,稍微工作有1年经验的同学,写起来都是666...

想看观察者模式的说明可以直接上菜鸟教程|观察者模式这个地址去看。

本篇文章其实就是一个简单的观察者模式,只是使用了模板的方式,把我们的回调接口进行了参数化,这样有什么好处呢?

好处当然是大大的有了。 平时我们在不同业务逻辑之间写观察者模式呢,都得写好多个,大家有没有发现,所有的被观察者Subject其实很多操作都是一样的。

本篇我们带来两种观察者模式:同步观察者和异步观察者

1、同步观察者

顾名思义,同步观察者其实就是不管是谁,触发了Subject的Update操作,该操作都是同步进行的,他会调用所有的观察者(Observer)的OnUpdate接口,来通知Observer处理改变操作。

如效果展示图中的第一个单次拉取页签,当我们点击拉取按钮时,就相当于触发了一次Subject对象的Update操作

2、异步观察者

异步观察者模式上和同步观察者基本一样,只是在事件处理上有稍微不同

  1. 执行Update操作是由Subject自己去完成的
  2. 调用Observer的OnUpdate回调接口时,处于工作线程中
  3. 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、获取观察者指针*

通过一个门面接口获取观察者指针

  1. 调用ISignal的Attach接口,就可以把自己添加到观察者列表。
  2. 调用ISignal的RequestData接口,就可以拉取数据。
  3. 调用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;
};

通过以上四步,就可以很方便的实现一个现在业务中的观察者,是不是很简单呢,编写过程中,需要完成这几个地方

  1. 需要定义我们回调函数的参数结构
  2. 需要实例化一个被观察者接口类
  3. 实例化一个业务观察者
  4. 做一个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界面

定时回调功能测试界面

  1. on_pushButton_clicked槽函数只是为了把当前线程唤醒,并定时回调
  2. 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);
}

以上讲解就是我们观察者的实现了,如果有疑问欢迎提出

五、相关文章

菜鸟教程|观察者模式

如果您觉得文章不错,不妨给个打赏,写作不易,感谢各位的支持。您的支持是我最大的动力,谢谢!!!

很重要--转载声明

  1. 本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者:朝十晚八 or Twowords

  2. 如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。


C++屌屌的观察者模式-同步回调和异步回调的更多相关文章

  1. Python并发编程06 /阻塞、异步调用/同步调用、异步回调函数、线程queue、事件event、协程

    Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件event.协程 目录 Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件 ...

  2. 通知url必须为直接可访问的url,不能携带参数 异步接收微信支付结果通知的回调地址 不能携带参数。 回调地址后是否可以加自定义参数 同步回调地址 异步回调地址 return_url和notify_url的区别

    [微信支付]微信小程序支付开发者文档 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_7 通知url必须为直接可访问的 ...

  3. 支付回调地址 同步回调地址 异步回调地址 return_url和notify_url的区别

    [微信支付]JSAPI支付开发者文档 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_16&index=10 退款结果通知 ...

  4. [Effective JavaScript 笔记]第67条:绝不要同步地调用异步的回调函数

    设想有downloadAsync函数的一种变种,它持有一个缓存(实现为一个Dict)来避免多次下载同一个文件.在文件已经被缓存的情况下,立即调用回调函数是最优选择. var cache=new Dic ...

  5. GIL全局解释器锁,线程池与进程池 同步异步,阻塞与非阻塞,异步回调

    GIL全局解释器锁 1.什么是GIL 官方解释:'''In CPython, the global interpreter lock, or GIL, is a mutex that prevents ...

  6. 委托(delegate)的三种调用方式:同步调用,异步调用,异步回调(转载)

    下面为即将被调用的方法: public delegate int AddHandler(int a,int b); public class 加法类 { public static int Add(i ...

  7. C#“同步调用”、“异步调用”、“异步回调”

    本文将主要通过“同步调用”.“异步调用”.“异步回调”三个示例来讲解在用委托执行同一个“加法类”的时候的的区别和利弊. 首先,通过代码定义一个委托和下面三个示例将要调用的方法: ); //模拟该方法运 ...

  8. 整理 C#(同步调用、异步调用、异步回调)

    //闲来无事,巩固同步异步方面的知识,以备后用,特整理如下: class Program { static void Main(string[] args) { //同步调用 会阻塞当前线程,一步一步 ...

  9. boost库学习随记六:使用同步定时器、异步定时器、bind、成员函数回调处理、多线程的同步处理示例等

    一.使用同步定时器 这个示例程序通过展示如何在一个定时器执行一个阻塞等待. //makefile #-------------------------------------------------- ...

随机推荐

  1. Angular常用指令

    安装Node 先去Node官网下载并安装Node.js Install the Angular CLI(安装Angular CLI) npm install -g @angular/cli Creat ...

  2. WPF ListView控件设置奇偶行背景色交替变换以及ListViewItem鼠标悬停动画

    原文:WPF ListView控件设置奇偶行背景色交替变换以及ListViewItem鼠标悬停动画 利用WPF的ListView控件实现类似于Winform中DataGrid行背景色交替变换的效果,同 ...

  3. Binding控件某个属性

    <Grid Margin="60"> <Grid.RowDefinitions> <RowDefinition Height="*" ...

  4. strlen, wcslen, _mbslen, _mbslen_l, _mbstrlen, _mbstrlen_l, setlocale(LC_CTYPE, "Japanese_Japan")(MSDN的官方示例)

    // crt_strlen.c // Determine the length of a string. For the multi-byte character // example to work ...

  5. C/C++使用libcurl库发送http请求(get和post可以用于请求html信息,也可以请求xml和json等串)

    C++要实现http网络连接,需要借助第三方库,libcurl使用起来还是很方便的 环境:win32 + vs2015 如果要在Linux下使用,基本同理 1,下载编译libcurl 下载curl源码 ...

  6. 图像滤镜艺术---保留细节的磨皮滤镜之PS实现

    原文:图像滤镜艺术---保留细节的磨皮滤镜之PS实现 目前,对于人物照片磨皮滤镜,相信大家没用过也听过吧,这个滤镜的实现方法是多种多样,有难有简,有好有差,本人经过长时间的总结,得出了一种最简单,效果 ...

  7. 图像滤镜艺术---Swirl滤镜

    原文:图像滤镜艺术---Swirl滤镜 Swirl Filter Swirl 滤镜是实现图像围绕中心点(cenX,cenY)扭曲旋转的效果,效果图如下: 原图 效果图 代码如下:         // ...

  8. Python写的嗅探器——Pyside,Scapy

    使用Python的Pyside和Scapy写的嗅探器原型,拥有基本框架,但是功能并不十分完善,供参考. import sys import time import binascii from PySi ...

  9. Excel的Range对象(C#)

    原文:Excel的Range对象(C#) Range 对象是 Excel 应用程序中最经常使用的对象:在操作 Excel 内的任何区域之前,都需要将其表示为一个 Range 对象,然后使用该 Rang ...

  10. 《C标准库》阅读笔记

    <assert.h>的断言函数适合于用来调试,实际产品中难以使用. #define NDEBUG 可禁用断言. #undef NDEBUG 可打开断言. 我自己写的一个例子: #inclu ...