前言

估计 2020 年写应用程序的机会比较多,之前一直在做嵌入式驱动程序和Android系统定制方面的工作,在应用程序方面积累的不是很多,因此迫切需要多学学应用编程这方面的知识。

之前在写小的应用程序的时候,总感觉会有更好的实现方式解耦,当时只是觉得要解决我所面临的瓶颈,可能需要找几个比较优秀的开源代码,多学习学习。因为一个偶然的机会接触设计模式之后,我嘞个去,这不就是可以解决困扰我很长时间的解耦问题的最佳解决方案吗?哈哈,在此写下自己的学习心得便于日后复习使用。

引用百度百科:软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。

观察者模式

简单介绍

观察者模式非常像我之前接触的 MQTT 协议中的发布/订阅机制,以及和 Android 中的广播机制。它们都是对某种消息\事件感兴趣就注册监听该消息\事件变化的场景。

借助该设计模式可以实现:产生事件的专注于事件检测逻辑,处理事件的专注于事件处理逻辑,不同的事件检测由不同的逻辑实现,不同的事件处理也由不同的事件处理逻辑实现,他们之间借助观察者模式即可实现联动。

因此可以将产生事件和处理事件的代码进行解耦隔离,各自处理互不干扰,这样实现的代码更加清晰明了和易于维护与扩展。如果我们在工作当中遇到类似的场景即可应用该设计模式。

应用场景

例:多对多,互联网领域,一个微博里面的某个明星即可认为是被观察者,关注该微博的粉色即可认为是观察者,当明星发布一条动态,那么所有关注其的粉丝都会收到该动态。多对多的场景,粉丝也可以同时关注其它的明星动态。(当然实际情况会更加复杂些,因为粉色自己也可以作为被观察者被人关注动态,这里只是举例说明,便于理解)

例:一对多,智能家居领域,需要实现离家安防模式,短信告警逻辑,视频录音录像逻辑,现场声光告警逻辑均可作为观察者,如借助人体红外感应实现的非法入侵检测逻辑功能,作为被观察者。当被观察者触发事件后(检测到非法入侵),那么就会触发短信告警、启动视频录音录像、启动现场喇叭和警示灯等功能逻辑。

原理说明

观察者设计模式是通过 C++ 面向对象的机制实现,主要是借助了继承的特性(继承权限、子类转换父类、纯虚函数),以及辅助用的模板类 list 实现。设计思想是首先编写两个类作为框架:

一个是被观察者类:

  1. 主要是维护一个观察者列表,用于记录有哪些观察者需要通知,该列表是 private 的,仅限于被观察者类框架自己维护。
  2. 提供增加和移除观察者的接口,用于登记和删除观察者,该接口是 public 的,所有对象都可以调用。
  3. 提供通知事件接口,用于通过遍历观察者列表,依次逐个调用观察者的事件回调来实现通知各个观察者,该接口是protected的,仅限于并且只应由派生类(即某个具体的被观察者)在产生事件时调用(这里决定了观察者回调函数不可阻塞和执行太复杂的代码,否则会影响后续的观察者事件通知不及时,也会影响被观察者的通知事件接口的执行事件时间长短)。
  4. 这里的回调主要是通过将继承了观察者的对象转换为其父类即观察者之后,再调用观察者的事件回调函数实现。

一个是观察者类:

  1. 提供一个事件回调纯虚函数,由某个具体的观察者继承实现,将会由被观察者产生事件的时候调用,由此得到通知。
  2. 可以增加一些额外的数据结构,如事件类型等等。

被观察者的具体实现

  1. 某个对象继承被观察者类。
  2. 产生某些事件时,其父类的通知事件接口,传入相应的参数即可。

观察者的具体实现

  1. 启动一个线程,利用队列和条件变量来让线程等待事件 。
  2. 某个对象继承观察者者类,并实现父类的事件回调的纯虚函数。
  3. 当事件触发时,将事件写入队列,并通过条件变量唤醒线程做业务处理。

示例代码

被观察者类框架

/**
******************************************************************************
* @文件 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++ 设计模式 - 观察者模式的更多相关文章

  1. 再起航,我的学习笔记之JavaScript设计模式02

    我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 我们 ...

  2. 再起航,我的学习笔记之JavaScript设计模式01

    我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 在通 ...

  3. 再起航,我的学习笔记之JavaScript设计模式03

    我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 上一 ...

  4. 再起航,我的学习笔记之JavaScript设计模式04

    我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 上回 ...

  5. 再起航,我的学习笔记之JavaScript设计模式05(简单工程模式)

    我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前几 ...

  6. 再起航,我的学习笔记之JavaScript设计模式06(工厂方法模式)

    上一次已经给大家介绍了简单工厂模式,相信大家对创建型设计模式有了初步的了解,本次我将给大家介绍的是工厂方法模式. 工厂方法模式 工厂方法模式(Factory Method):通过对产品类的抽象使其创建 ...

  7. 再起航,我的学习笔记之JavaScript设计模式06(抽象工厂模式)

    我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前两 ...

  8. 再起航,我的学习笔记之JavaScript设计模式08(建造者模式)

    我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前几 ...

  9. 再起航,我的学习笔记之JavaScript设计模式09(原型模式)

    我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 我们 ...

  10. 再起航,我的学习笔记之JavaScript设计模式07(抽象工厂模式)

    我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前两 ...

随机推荐

  1. Java 编码那些事(一)

    编码 做Web的同学,最开始一定遇到过乱码问题,工作这么久,一定听说过Unicode, GB2312等编码.典型的记事本选择的四种选项:ANSI,Unicode,Unicode big endian, ...

  2. 强软弱引用,ThreadLocal和内存泄漏

    强引用 写法:Object obj=new Object() 引用强度:最强 只要被引用着,就不会被gc(垃圾回收)回收掉. 软引用 写法:SoftReference<String> sr ...

  3. XTDrone和PX4学习期间问题记录(一)

    XTDrone和PX4学习期间问题记录(一) Written By PiscesAlpaca 前言: 出现问题可以去官方网站http://ceres-solver.org/index.html查看文档 ...

  4. Go语言核心36讲53

    你好,我是郝林. 在2019年的春节来临之际,我恰好也更新完了专栏所有的配图和思考题答案.希望这些可以帮助到你,在新的一年中,祝你新年快乐,Go语言学习之路更加顺利. 基础概念篇 1. Go语言在多个 ...

  5. centos7 uwsgi 加入系统服务

    生产环境中采用nginx + uwsgi + django 来部署web服务,这里需要实现uwsgi的启动和停止,简单的处理方式可以直接在命令行中启动和kill掉uwsgi服务,但为了更安全.方便的管 ...

  6. 我开发的开源项目,让.NET7中的EFCore更轻松地使用强类型Id

    在领域驱动设计(DDD)中,有一个非常重要的概念:"强类型Id".使用强类型Id来做标识属性的类型会比用int.Guid等通用类型能带来更多的好处.比如有一个根据根据Id删除用户的 ...

  7. tcp/udp 协议特性和三次握手

    一.TCP/UDP协议特性1)TCP特性:工作在传输层.建立连接.可靠的.错误检查 2)UDP特性:工作在传输层.不需要连接.不可靠的.有限的错误检查.传输性能高 2.控制位及确认号解释 控制位:由6 ...

  8. 【深入浅出SpringCloud原理及实战】「SpringCloud-Alibaba系列」微服务模式搭建系统基础架构实战指南及版本规划踩坑分析

    Spring Cloud Alibaba Nacos Discovery Spring Boot 应用程序在服务注册与发现方面提供和 Nacos 的无缝集成. 通过一些简单的注解,您可以快速来注册一个 ...

  9. SQLMap入门——获取数据库的所有用户

    列出数据库中的所有用户 在当前用户有权读取包含所有用户的表的权限时,使用该命令列出所有管理用户 python sqlmap.py -u http://localhost/sqli-labs-maste ...

  10. django.db.migrations.exceptions.NodeNotFoundError: Migration apitest.0001_initial dependencies reference nonexistent parent node ('product', '0001_initial')

    执行python manage.py makemigrations时出现以下错误 D:\autotestplat>python manage.py makemigrations Tracebac ...