浅谈项目开发中的模块化、解耦、封装
目录
模块化
解耦
封装
模块化
模块化是指将功能相关的代码和数据组织成独立的模块,以便于开发的任务分割与安排、独立测试、后期维护以及后续别的项目有相同功能时的可移植,大大缩短开发时间,避免重复造轮子。当你模块化到了极致,简单的功能重复项目可以做到1-2天上交测试。
总言之,模块化可以提高代码的可重用性和可测试性,并简化系统的复杂性与提高系统的隐秘性。
先举个代码栗子,简单了解下“模块”,如下低配版按键功能模块示意代码:
#include "delay.h" /* 示意延时头文件 */
#include "readIO.h" /* 示意读取KEY状态头文件 */
#include "led.h" /* 示意KEY触发的处理函数头文件 *//*按键功能示意代码*/
void KEY_Scan(void)
{if(0 == ReadKEY()){Delayms(20); /* 仅仅示意,同学们别再用堵塞延时啦,学学状态机 */if(0 == ReadKEY()){LED(OPEN); /* 实现按键短按亮灯 */}}
}
这是一个关于按键功能的模块,是一个未经过解耦的按键模块,里面还能分割成按键扫描功能模块、IO状态读取模块、按键触发的其他功能模块。
再举个栗子:每个大模块还能继续分割成多个小模块,比如(1+2)为大模块,把“1”与“2”分割出来成为小模块,大模块中主要实现“+”这个功能,以及如何把“1”与“2”甚至“3”“4”“9”的功能接入进来。如用户交互大模块,其中的用户输入可能是按键、触摸屏、通讯协议等,反馈可能是显示图片到OLED屏、串口屏、打印文字信息等等。由用户输入进而实现反馈就是“+”,按键、触摸屏、通讯协议、显示图片到OLED屏、串口屏、打印文字信息就是“1”“2”“3”...
再举个栗子:如一个温控设备,可以分成数据采集模块、用户交互模块、温度控制模块等大模块。这些大模块都可以分派给独立的工程师去进行开发,每位工程师只需了解自己负责模块的功能与接口需求,无需知道这些模块最终被用在哪,当然这是基于系统的隐秘性的。如果你是独立开发或是项目架构师,你要尽可能的去分析每个模块将会用在哪,需要提供什么功能什么接口来提高可重用性。什么需要公开什么需要私有,这里就涉及到了下面讲到的“封装”了。
解耦
解耦是解除耦合的简称,是指将代码中的不相关部分分离开来,以减少模块之间的依赖性,从而提高代码的可维护性、可兼容性和可扩展性。解耦可以通过使用接口、事件驱动编程和依赖注入等技术来实现。
总言之,始终贯彻着“事不关己高高挂起”的核心思想。
何为依赖性?有大部分同学的按键扫描驱动写法都是直接读取GPIO电平的。啊?不是这样写那怎么写啊?看到这里是不是就依赖着“直接读取GPIO电平”了,而且还需要在按键扫描驱动里添加关于“读取GPIO电平”的库函数头文件。
何为耦合?上面说的“直接读取GPIO电平”以及按键触发了短按、短按抬起等等时需要处理的事件,比如需要实现短按亮灯、短按抬起灭灯等,则还需要把关于控制灯功能的头文件添加进来。哇塞,大杂烩,全都到碗里来。
何为接口技术实现解耦?如下升级版按键功能模块示意代码:
/****************************************外部接口****************************************/
#include "delay.h" /* 示意延时头文件 */
#include "readIO.h" /* 示意读取KEY状态头文件 */
#include "handle.h" /* 示意KEY触发的处理函数头文件 */static void KEY_Delayms(unsigned short ms)
{//添加延时函数
}static unsigned char ReadKEY0(void)
{//添加读取KEY0状态的函数
}static void KEY0_Handle(void)
{//添加读取KEY0触发的处理函数
}
/****************************************外部接口 End****************************************//*按键功能示意代码*/
void KEY_Scan(void)
{if(0 == ReadKEY0()){KEY_Delayms(20); /* 仅仅示意,同学们别再用堵塞延时啦,学学状态机 */if(0 == ReadKEY0()){KEY0_Handle();}}
}
这种接口技术,仅仅在按键功能模块文件里进行解耦,但并不是真正完全解耦。优点是代码上没那么臃肿,把按键驱动功能与外部接口功能分隔开来。
为了实现真正解耦,有请函数指针上场,如下初级版单按键扫描驱动示意代码:
typedef void (*KEY_Delay)(unsigned char ms); /* 延时函数函数指针类型 */
typedef unsigned char (*ReadKEY)(void); /* 读取按键状态函数函数指针类型 */
typedef void (*KEY_Handle)(unsigned char keyState); /* 按键处理函数函数指针类型 */static void KEY_Delay_NULL(unsigned char ms){} /* 延时函数类型的空函数 */
static unsigned char ReadKEY_NULL(void){} /* 读取按键状态函数类型的空函数 */
static void KEY_Handle_NULL(unsigned char keyState){} /* 按键处理函数类型的空函数 */static KEY_Delay KEY_Delayms = KEY_Delay_NULL; /**< 延时函数 防止为野指针 */
static ReadKEY ReadKEY0 = ReadKEY_NULL; /**< 读取按键状态函数 防止为野指针 */
static KEY_Handle KEY0_Handle = KEY_Handle_NULL; /**< 按键处理函数 防止为野指针 *//*延时函数的注册函数*/
unsigned char Register_KEY_Delay(KEY_Delay function)
{if(KEY_Delay_NULL == KEY_Delayms){KEY_Delayms = function;return 0; /* 首次注册 */}KEY_Delayms = function;return 1; /* 非首次注册 */
}/*读取按键状态函数的注册函数*/
unsigned char Register_ReadKEY(ReadKEY function)
{if(ReadKEY_NULL == ReadKEY0){ReadKEY0 = function;return 0; /* 首次注册 */}ReadKEY0 = function;return 1; /* 非首次注册 */
}/*按键处理函数的注册函数*/
unsigned char Register_KEY_Handle(KEY_Handle function)
{if(KEY_Handle_NULL == KEY0_Handle){KEY0_Handle = function;return 0; /* 首次注册 */}KEY0_Handle = function;return 1; /* 非首次注册 */
}/*按键扫描示意代码*/
void KEY_Scan(void)
{if(0 == ReadKEY0()) /* 低电平有效 */{KEY_Delayms(20); /* 仅仅示意,同学们别再用堵塞延时啦,学学状态机 */if(0 == ReadKEY0()){KEY0_Handle(CLICK); /* 短按按下 */}}
}
由此写法可以看出,这个按键扫描的驱动文件里干净了许多,无需依赖别的头文件,仅有按键扫描的功能,其他部分由上层调用者去注册即可。这时就会有人说,这写法不是看起来更加复杂和繁琐吗?我知道你很急但你先别急,这仅仅只是初级版,我们了解完解耦之后,现在来看看下面的“封装”。
封装
封装是指将代码和数据结构隐藏在一个模块或类中,需要通过提供公共接口来访问和操作这些数据结构和功能。封装可以提高代码的安全性和保密性,降低调用者对代码内部功能具体如何实现的要求,调用者只需关注封装的功能和接口即可。
举个最简单的栗子,函数封装:
C文件内容/*冒泡排序*/
void Bubble_Sort(unsigned char* pBuf, unsigned short bufSize, unsigned char flag)
{//...具体就不实现了哈
}
H文件内容/*声明冒泡排序*/
void Bubble_Sort(unsigned char* pBuf, unsigned short bufSize, unsigned char flag);
冒泡排序函数封装好了,调用者只需了解接口怎么用即可,并不需要知道内部如何排的序。
现在以按键扫描驱动再举个大栗子,里面有面向对象与状态机结合的思想,如下为进阶版按键扫描驱动:
C文件内容#include "KEY_Driver.h"/*按键对象创建*/
void KEY_Create(_STR_KEY* str, unsigned char keyName, \unsigned char (*ReadKEY)(void), \void (*KEY_Handle)(unsigned char keyState))
{str->keyName = keyName;str->keyState = KEY_STEP_WAIT;str->count = 0;//...str->ReadKEY = ReadKEY;str->KEY_Handle = KEY_Handle;
}/*按键扫描*/
void KEY_Scan(_STR_KEY* str)
{switch(str->CheckStep){case KEY_STEP_WAIT:/*等待按下*/if(0 == str->ReadInputDataBit()) /* 电平有效 */{str->DownCount = str->Click_CountVal; /* 赋予短按按下计数值 */str->CheckStep = KEY_STEP_CLICK; /* 检测状态切换:检测按下 */}break;case KEY_STEP_CLICK:/*检测按下*/str->DownCount--;if(str->DownCount == 0) /* 短按按下计数已过(消抖) */{if(0 == str->ReadInputDataBit()) /* 电平仍然有效 */{str->State = KEY_STATE_CLICK; /* 状态切换:短按 */str->ActionFunc(str->ID, str->State); /* 执行函数 *///...str->CheckStep = KEY_STEP_LONG; /* 检测状态切换:检测长按 */}else /* 无效 */{str->CheckStep = KEY_STEP_WAIT; /* 检测状态切换:等待按下 */}}break;//...}
}
H文件(KEY_Driver.h)/**按键状态*/
typedef enum{KEY_STEP_WAIT,KEY_STATE_CLICK, /**< 短按 */KEY_STATE_CLICK_UP, /**< 短按抬起 *///...
}_enum_KEY_STATE;/**按键对象*/
typedef struct STR_KEY{unsigned char keyName; /**< 按键ID */unsigned char keyState; /**< 按键状态 */unsigned short count; /**< 计数器 *///...unsigned char (*ReadKEY)(void); /**< 读取按键状态函数函数指针 */void (*KEY_Handle)(unsigned char keyState); /**< 按键执行函数函数指针 */
}_STR_KEY;/*按键对象创建*/
void KEY_Create(_STR_KEY* str, unsigned char keyName, \unsigned char (*ReadKEY)(void), \void (*KEY_Handle)(unsigned char keyState));
/*按键扫描*/
void KEY_Scan(_STR_KEY* str);
这个版本的按键驱动是已经完全解耦了,扫描功能是实现了,也封装好了扫描功能,但是我们细看H文件,我们能看到“按键对象”的结构体全部成员(虽然我省略了一部分)。看到就看到啊,怎么了?正因为看到了,我可以根据这个结构体去反向分析代码实现,使得保密性下降。我还可以直接修改结构体成员的值,使得内部代码的运行逻辑乱套甚至崩溃跑飞,使得安全性下降。
那么还需要对结构体进行隐藏,说到结构体隐藏我知道的有两种方式,我们后面再讲。
接着再讲讲源文件,如果直接把源文件发给调用者,调用者可以直接查看并且修改源码,这是非常影响代码的保密性与安全性,当然开源代码就另当别论了。如果调用者在不了解源码的实现原理的情况下,随意修改会导致源码损坏无法运行的后果。所以有的代码还会再进一步封装生成静态库或动态链接库;
分享先到这里,希望能给大家带来启发与帮助。如果对内容存在疑问或想法,欢迎在评论区留言,我会积极回复大家的问题。在我的“经验分享”专栏中,还有很多实用的经验,欢迎一起探讨、一起学习。
浅谈项目开发中的模块化、解耦、封装相关推荐
- 浅谈实际开发中常用的分布式事物处理
浅谈实际开发中常用的分布式事物处理 文章目录 前言 一.分布式事物 二.常用方案 1.使用记录表+mq机制 前言 随着微服务的流行,越来越多系统不在是单体结构,根据业务和功能拆分成不同微服务,这就导致 ...
- 浅谈Android开发中的NFC功能
目录 1."NFC"的自我描述 1.1 NFC功能的基本概念 1.2 NFC功能的背景.特性及发展趋势 2.NFC的基础知识.基本原理 2.1 NFC的工作模式 2.2 实现NFC ...
- 浅谈软件开发中的假设条件
翻开第一篇聊假设条件的博客,发现已经快2年了.那篇主要涉及了点架构方面假设条件的东西,不是很全,今天开一篇聊一下软件开发中的假设条件.如果把假设条件限定在架构方面,稍显冷门.但如果将其扩展到整个软件开 ...
- 浅谈iOS 开发中的界面通信
在任何的软件开发中都离不开界面与界面之间的通信,界面通信的最直接的方法就是界面传值. 在开发过程中我们在页面传值时我们通常使用的方法有:属性传值法,block传值法,代理传值法,以及单例传值法,通知传 ...
- 浅谈Web开发中的6种技术
CSDN博客不再经常更新,更多优质文章请来 粉丝联盟网 FansUnion.cn! (FansUnion) Web开发中的6种技术 1.html 超文本标记语言,即HTML(Hypertext Mar ...
- 浅谈项目开发现状(一)
在现在的软件开发中,一些大的软件公司有充分的资金,所以他的公司人员组织架构能组成:需求分析团队(为了更好的了解用户的完整需求)--->研发团队(通过计算机语言来实现用户需求),方式如下: 虽然客 ...
- 浅谈游戏开发中逻辑与表现的分离
回顾之前做的几个Demo,做点总结. 一.做法: 为了更清晰,NRatel将 一个游戏对象类 拆分为 两个类,如下: 1.定义"纯粹的逻辑类". 基本职责:对游戏对象的&quo ...
- 浅谈敏捷开发中的设计
敏捷开发在当今业界已经大行其道,想要快速交付,采用敏捷开发方法似乎是最好的方式,是否必须要用这就另当别论了.敏捷开发以用户的需求进化为核心,采用迭代.循序渐进的方法进行软件开发,不过,想要真正做到快速 ...
- java怎么设置全局变量_浅谈Java开发中如何定义的全局变量
Static静态变量 在程序中任何变量或者代码都是在编译时,是由系统自动分配内存来存储的,而所谓静态就是指在编译后分配的内存会一直存在,直到程序退出时才会释放内存空间. static是静态修饰符.被s ...
- 浅谈软件开发工具CASE在软件项目开发中发挥的作用认识
浅谈软件开发工具CASE在软件项目开发中发挥的作用认识 内容摘要:阐述了CASE工具作为 一种开发环境在软件项目开发中所起到的开发及管理作用.CASE工具实际上是把原先由手工完成的开发过程转变为以自动 ...
最新文章
- 布道微服务_07服务调用追踪
- python numpy.arange() 函数的使用方法 (在给定间隔内返回均匀间隔的值)
- POJ - 3415 Common Substrings(长度不小于K的公共子串个数)
- LeetCode 394. 字符串解码(栈)
- flash 定义主舞台窗口大小
- 【Swift学习】Swift编程之旅---析构方法(十九)
- fatfs 文件属性_FATFS文件系统剖析(全).
- 研磨设计模式学习笔记1--简单工厂(SimpleFactory)
- KEIL5下载程序失败解决办法
- 第一个hadoop程序(hadoop2.4.0集群+Eclipse环境)
- 天地图key申请_国家地理信息公共服务平台 天地图
- python视频图片识别算法_python利用Opencv进行人脸识别(视频流+图片)
- 如何把操作系统迁移到新电脑/硬盘
- 水星USB无线网卡mw150us苹果macOS系统驱动成功
- K3ERP web登录问题解决
- 新学编程之掌握基本概念
- VIPER,更清晰的架构,解决复用和测试问题的利器系列1:VIPER架构演进史
- algebraic reconstruction technique (ART)算法
- mysql 输出名称_MySQL常用的SQL语句//输出所有信息showfullfieldsfrom'表名称';//改表
- 教你如何把视频转成序列图片的实用技巧
热门文章
- 几大主流云存储服务深层对比
- 情窦初开,第一次学着当一个人的女朋友时,我很狼狈
- 让人人都能用得起基因检测:23魔方发布第四代新品
- iPhone,iPad型号,屏幕尺寸,分辨率小结
- 汉字拼音转换工具(Python版)
- PT13-47页蓝色互联网年中晋升竞聘工作汇报PPT模板
- 2022年G2电站锅炉司炉考试报名及G2电站锅炉司炉复审考试
- 关系型数据库与非关系型数据库的区别汇总
- 麦库记事Android客户端(V1.4.3)评测
- Sequence Level Training with Recurrent Neural Networks-学习笔记