【学习笔记】C/C++ 设计模式 - 观察者模式
前言
估计 2020 年写应用程序的机会比较多,之前一直在做嵌入式驱动程序和Android系统定制方面的工作,在应用程序方面积累的不是很多,因此迫切需要多学学应用编程这方面的知识。
之前在写小的应用程序的时候,总感觉会有更好的实现方式解耦,当时只是觉得要解决我所面临的瓶颈,可能需要找几个比较优秀的开源代码,多学习学习。因为一个偶然的机会接触设计模式之后,我嘞个去,这不就是可以解决困扰我很长时间的解耦问题的最佳解决方案吗?哈哈,在此写下自己的学习心得便于日后复习使用。
引用百度百科:软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
观察者模式
简单介绍
观察者模式非常像我之前接触的 MQTT 协议中的发布/订阅机制,以及和 Android 中的广播机制。它们都是对某种消息\事件感兴趣就注册监听该消息\事件变化的场景。
借助该设计模式可以实现:产生事件的专注于事件检测逻辑,处理事件的专注于事件处理逻辑,不同的事件检测由不同的逻辑实现,不同的事件处理也由不同的事件处理逻辑实现,他们之间借助观察者模式即可实现联动。
因此可以将产生事件和处理事件的代码进行解耦隔离,各自处理互不干扰,这样实现的代码更加清晰明了和易于维护与扩展。如果我们在工作当中遇到类似的场景即可应用该设计模式。
应用场景
例:多对多,互联网领域,一个微博里面的某个明星即可认为是被观察者,关注该微博的粉色即可认为是观察者,当明星发布一条动态,那么所有关注其的粉丝都会收到该动态。多对多的场景,粉丝也可以同时关注其它的明星动态。(当然实际情况会更加复杂些,因为粉色自己也可以作为被观察者被人关注动态,这里只是举例说明,便于理解)
例:一对多,智能家居领域,需要实现离家安防模式,短信告警逻辑,视频录音录像逻辑,现场声光告警逻辑均可作为观察者,如借助人体红外感应实现的非法入侵检测逻辑功能,作为被观察者。当被观察者触发事件后(检测到非法入侵),那么就会触发短信告警、启动视频录音录像、启动现场喇叭和警示灯等功能逻辑。
原理说明
观察者设计模式是通过 C++ 面向对象的机制实现,主要是借助了继承的特性(继承权限、子类转换父类、纯虚函数),以及辅助用的模板类 list 实现。设计思想是首先编写两个类作为框架:
一个是被观察者类:
- 主要是维护一个观察者列表,用于记录有哪些观察者需要通知,该列表是 private 的,仅限于被观察者类框架自己维护。
- 提供增加和移除观察者的接口,用于登记和删除观察者,该接口是 public 的,所有对象都可以调用。
- 提供通知事件接口,用于通过遍历观察者列表,依次逐个调用观察者的事件回调来实现通知各个观察者,该接口是protected的,仅限于并且只应由派生类(即某个具体的被观察者)在产生事件时调用(这里决定了观察者回调函数不可阻塞和执行太复杂的代码,否则会影响后续的观察者事件通知不及时,也会影响被观察者的通知事件接口的执行事件时间长短)。
- 这里的回调主要是通过将继承了观察者的对象转换为其父类即观察者之后,再调用观察者的事件回调函数实现。
一个是观察者类:
- 提供一个事件回调纯虚函数,由某个具体的观察者继承实现,将会由被观察者产生事件的时候调用,由此得到通知。
- 可以增加一些额外的数据结构,如事件类型等等。
被观察者的具体实现
- 某个对象继承被观察者类。
- 产生某些事件时,其父类的通知事件接口,传入相应的参数即可。
观察者的具体实现
- 启动一个线程,利用队列和条件变量来让线程等待事件 。
- 某个对象继承观察者者类,并实现父类的事件回调的纯虚函数。
- 当事件触发时,将事件写入队列,并通过条件变量唤醒线程做业务处理。
示例代码
被观察者类框架
/**
******************************************************************************
* @文件 Subject.h
* @版本 V1.0.0
* @日期
* @概要 被观察者框架实现
* @作者 lmx
* @邮箱 lovemengx@qq.com
* @博客 https://me.csdn.net/lovemengx
******************************************************************************
**/
#ifndef __SUBJECT_H
#define __SUBJECT_H
#include <iostream>
#include <list>
#include <mutex>
#include "ObServer.h"
using namespace std;
// 被观察者(谁需要发布主题, 则继承并且在合适的时候调用通知方法)
class Subject
{
private: // 私有的, 由框架自己维护的数据结构, 主要用于维护观察者列表
mutex list_mutex;
list <ObServer*> observer_list;
protected: // 保护的, 由派生类使用的接口, 借此发布通知(仅对派生类可见, 但不公开, 避免被其它地方通过派生类使用)
int notice(ObServer::OBSERVER_EVENT_TYPE_E type, int argc);
public: // 公有的, 用于将观察者加入或移除观察列表的接口
int attach(ObServer* observer);
int detach(ObServer* observer);
};
#endif
/**
******************************************************************************
* @文件 Subject.cpp
* @版本 V1.0.0
* @日期
* @概要 被观察者的框架实现
* @作者 lmx
* @邮箱 lovemengx@qq.com
* @博客 https://me.csdn.net/lovemengx
******************************************************************************
**/
#include "Subject.h"
// 通知观察者
int Subject::notice(ObServer::OBSERVER_EVENT_TYPE_E type, int argc)
{
// 便利所有观察者对象, 并调用其 onEvent 方法来进行通知
// 如果某个观察者对象中的 onEvent 实现的代码出现阻塞或执行时间过长, 将会影响此循环执行时间
// 一种解决方式是为每个观察者事件回调函数创建一个线程来执行
// 另一种解决方式是要求具体的观察者对象各自维护一个事件队列和一个处理事件的线程, 在 onEvent 中将事件存入队列并唤醒处理该事件的线程
list_mutex.lock();
for (list<ObServer*>::iterator iter = observer_list.begin(); iter != observer_list.end(); ++iter)
{
(*iter)->onEvent(type, argc);
}
list_mutex.unlock();
return 0;
}
// 将指定的观察者附加到列表中
int Subject::attach(ObServer* observer)
{
if (NULL == observer)
return -1;
list_mutex.lock();
observer_list.push_back(observer);
list_mutex.unlock();
return 0;
}
// 将指定的观察者从列表中分离
int Subject::detach(ObServer* observer)
{
if (NULL == observer)
return -1;
list_mutex.lock();
observer_list.remove(observer);
list_mutex.unlock();
return 0;
}
观察者类框架
/**
******************************************************************************
* @文件 ObServer.h
* @版本 V1.0.0
* @日期
* @概要 观察者框架实现
* @作者 lmx
* @邮箱 lovemengx@qq.com
* @博客 https://me.csdn.net/lovemengx
******************************************************************************
**/
#ifndef __OBSERVER_H
#define __OBSERVER_H
// 观察者-谁需要观察则继承该类
class ObServer
{
public:
// 观察的事件类型
typedef enum {
OBSERVER_EVENT_TYPE_A,
OBSERVER_EVENT_TYPE_B,
}OBSERVER_EVENT_TYPE_E;
public:
// 当有事件变动时, 会由被观察者调用, 从而让观察者得到事件通知
virtual void onEvent(OBSERVER_EVENT_TYPE_E type, int argc) = 0;
};
#endif // !__SUBSCRIBE_H
具体的被观察者
/**
******************************************************************************
* @文件 SubjectTheme.h
* @版本 V1.0.0
* @日期
* @概要 被观察者的具体实现
* @作者 lmx
* @邮箱 lovemengx@qq.com
* @博客 https://me.csdn.net/lovemengx
******************************************************************************
**/
#ifndef __SUBJECT_THEME_H
#define __SUBJECT_THEME_H
#include "../Subject.h"
using namespace std;
// 某具体的被观察者
class SubjectTheme : public Subject
{
private:
// 利用线程模拟事件产生
bool mThreadTimerExitFlags;
thread mThreadTimer;
static void ThreadTimer(SubjectTheme* pSubjectTheme);
public:
void start();
void stop();
};
#endif
/**
******************************************************************************
* @文件 SubjectTheme.cpp
* @版本 V1.0.0
* @日期
* @概要 被观察者的具体实现
* @作者 lmx
* @邮箱 lovemengx@qq.com
* @博客 https://me.csdn.net/lovemengx
******************************************************************************
**/
#include <windows.h>
#include "SubjectTheme.h"
// 利用线程实现简单的定时产生事件的机制
void SubjectTheme::ThreadTimer(SubjectTheme* pSubjectTheme)
{
unsigned int count = 0;
// 间隔一定的时间出发一次事件通知
while (!pSubjectTheme->mThreadTimerExitFlags)
{
count++;
printf("SubjectTheme: notice...\n");
pSubjectTheme->notice(ObServer::OBSERVER_EVENT_TYPE_A, count);
Sleep(1000);
}
return;
}
// 启动模拟
void SubjectTheme::start()
{
printf("SubjectTheme: start...\n");
mThreadTimerExitFlags = false;
mThreadTimer = thread(ThreadTimer, this);
return;
}
// 停止模拟
void SubjectTheme::stop()
{
printf("SubjectTheme: stop...\n");
mThreadTimerExitFlags = true;
mThreadTimer.join();
return;
}
具体的观察者1
/**
******************************************************************************
* @文件 ObServerSubscribeA.h
* @版本 V1.0.0
* @日期
* @概要 某具体对象作为观察者的示例
* @作者 lmx
* @邮箱 lovemengx@qq.com
* @博客 https://me.csdn.net/lovemengx
******************************************************************************
**/
#ifndef __OBSERVER_SUBSCRIBEA_H
#define __OBSERVER_SUBSCRIBEA_H
#include "../ObServer.h"
class ObServerSubscribeA: public ObServer
{
private:
// 通过 Observer 继承
virtual void onEvent(OBSERVER_EVENT_TYPE_E type, int argc) override;
};
#endif
/**
******************************************************************************
* @文件 ObServerSubscribeA.h
* @版本 V1.0.0
* @日期
* @概要 某具体对象作为观察者的示例
* @作者 lmx
* @邮箱 lovemengx@qq.com
* @博客 https://me.csdn.net/lovemengx
******************************************************************************
**/
#include <iostream>
#include "ObServerSubscribeA.h"
// 通过 Observer 继承
void ObServerSubscribeA::onEvent(OBSERVER_EVENT_TYPE_E type, int argc)
{
printf("ObServerSubscribeA: onEvent type:%d argc:%d\n", type, argc);
}
具体的观察者2
/**
******************************************************************************
* @文件 ObServerSubscribeB.h
* @版本 V1.0.0
* @日期
* @概要 某具体对象作为观察者的示例
* @作者 lmx
* @邮箱 lovemengx@qq.com
* @博客 https://me.csdn.net/lovemengx
******************************************************************************
**/
#ifndef __OBSERVER_SUBSCRIBEB_H
#define __OBSERVER_SUBSCRIBEB_H
#include "../ObServer.h"
class ObServerSubscribeB : public ObServer
{
private:
// 通过 Observer 继承
virtual void onEvent(OBSERVER_EVENT_TYPE_E type, int argc) override;
};
#endif
/**
******************************************************************************
* @文件 ObServerSubscribeB.h
* @版本 V1.0.0
* @日期
* @概要 某具体对象作为观察者的示例
* @作者 lmx
* @邮箱 lovemengx@qq.com
* @博客 https://me.csdn.net/lovemengx
******************************************************************************
**/
#include <iostream>
#include "ObServerSubscribeB.h"
// 通过 Observer 继承
void ObServerSubscribeB::onEvent(OBSERVER_EVENT_TYPE_E type, int argc)
{
printf("ObServerSubscribeB: onEvent type:%d argc:%d\n", type, argc);
}
单元测试代码
/**
******************************************************************************
* @文件 test_ObServer.h
* @版本 V1.0.0
* @日期
* @概要 观察者设计模式单元测试
* @作者 lmx
* @邮箱 lovemengx@qq.com
* @博客 https://me.csdn.net/lovemengx
******************************************************************************
**/
#include <iostream>
#include <windows.h>
#include "test_ObServer.h"
#include "Theme/SubjectTheme.h"
#include "Subscribe/ObServerSubscribeA.h"
#include "Subscribe/ObServerSubscribeB.h"
void TestObServer::test(void)
{
SubjectTheme mSubjectTheme;
ObServerSubscribeA mObServerSubscribeA;
ObServerSubscribeB mObServerSubscribeB;
mSubjectTheme.attach(&mObServerSubscribeA);
mSubjectTheme.attach(&mObServerSubscribeB);
mSubjectTheme.start();
Sleep(10 * 1000);
mSubjectTheme.stop();
return;
}
执行结果
【学习笔记】C/C++ 设计模式 - 观察者模式的更多相关文章
- 再起航,我的学习笔记之JavaScript设计模式02
我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 我们 ...
- 再起航,我的学习笔记之JavaScript设计模式01
我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 在通 ...
- 再起航,我的学习笔记之JavaScript设计模式03
我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 上一 ...
- 再起航,我的学习笔记之JavaScript设计模式04
我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 上回 ...
- 再起航,我的学习笔记之JavaScript设计模式05(简单工程模式)
我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前几 ...
- 再起航,我的学习笔记之JavaScript设计模式06(工厂方法模式)
上一次已经给大家介绍了简单工厂模式,相信大家对创建型设计模式有了初步的了解,本次我将给大家介绍的是工厂方法模式. 工厂方法模式 工厂方法模式(Factory Method):通过对产品类的抽象使其创建 ...
- 再起航,我的学习笔记之JavaScript设计模式06(抽象工厂模式)
我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前两 ...
- 再起航,我的学习笔记之JavaScript设计模式08(建造者模式)
我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前几 ...
- 再起航,我的学习笔记之JavaScript设计模式09(原型模式)
我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 我们 ...
- 再起航,我的学习笔记之JavaScript设计模式07(抽象工厂模式)
我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前两 ...
随机推荐
- .NET 7 中 LINQ 的疯狂性能提升
LINQ 是 Language INtegrated Query 单词的首字母缩写,翻译过来是语言集成查询.它为查询跨各种数据源和格式的数据提供了一致的模型,所以叫集成查询.由于这种查询并没有制造新的 ...
- easyclick 学习
什么是EC EasyClick 写自动化脚本,使用的编辑器 Idea 运行模式有两种:无障碍模式.代理模式 注释 三种类型: 1./** 对程序作介绍,解释说明用 / 2./* 多行注释 第二行 / ...
- SpringBoot 01: JavaConfig + @ImportResource + @PropertyResource
springboot的前置知识:通过注解创建对象和读取配置文件 1. JavaConfig 设计思想 使用java类作为xml配置文件的替代,是配置spring容器的纯java的方式 可以创建java ...
- [CS61A] Lecture 4. Higher-Order Functions & Project 1: The Game of Hog
[CS61A] Lecture 4. Higher-Order Functions & Project 1: The Game of Hog Lecture Lecture 4. Higher ...
- 使用SVN搭建本地版本控制仓库
使用SVN搭载本地版本控制仓库[转] 如果是在公司,都是有云服务器,项目负责人都是把项目放在服务器上,我们直接用SVN地址就可以实现更新和下载项目源码,那么如果我们自己想使用SVN在本机管理自己写的一 ...
- Mysql通过Merge引擎进行分表
Mysql通过Merge引擎进行分表 转载: https://www.jianshu.com/p/9420a6a8ae2e https://www.cnblogs.com/xbq8080/p/6628 ...
- Pthread 并发编程(三)——深入理解线程取消机制
Pthread 并发编程(三)--深入理解线程取消机制 基本介绍 线程取消机制是 pthread 给我们提供的一种用于取消线程执行的一种机制,这种机制是在线程内部实现的,仅仅能够在共享内存的多线程程序 ...
- (C++) 笔记 C++11 std::mutex std::condition_variable 的使用
#include <atomic> #include <chrono> #include <condition_variable> #include <ios ...
- Python: 你所不知道的星号 * 用法
以下内容为本人的学习笔记,如需要转载,请声明原文链接微信公众号「englyf」https://mp.weixin.qq.com/s/FHyosiG_tegF5NRUEs7UdA 本文大概 1193 个 ...
- React Server Component: 混合式渲染
作者:谢奇璇 React 官方对 Server Comopnent 是这样介绍的: zero-bundle-size React Server Components. 这是一种实验性探索,但相信该探索 ...