目录

模块化

解耦

封装


模块化

模块化是指将功能相关的代码和数据组织成独立的模块,以便于开发的任务分割与安排、独立测试、后期维护以及后续别的项目有相同功能时的可移植,大大缩短开发时间,避免重复造轮子。当你模块化到了极致,简单的功能重复项目可以做到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. 浅谈实际开发中常用的分布式事物处理

    浅谈实际开发中常用的分布式事物处理 文章目录 前言 一.分布式事物 二.常用方案 1.使用记录表+mq机制 前言 随着微服务的流行,越来越多系统不在是单体结构,根据业务和功能拆分成不同微服务,这就导致 ...

  2. 浅谈Android开发中的NFC功能

    目录 1."NFC"的自我描述 1.1 NFC功能的基本概念 1.2 NFC功能的背景.特性及发展趋势 2.NFC的基础知识.基本原理 2.1 NFC的工作模式 2.2 实现NFC ...

  3. 浅谈软件开发中的假设条件

    翻开第一篇聊假设条件的博客,发现已经快2年了.那篇主要涉及了点架构方面假设条件的东西,不是很全,今天开一篇聊一下软件开发中的假设条件.如果把假设条件限定在架构方面,稍显冷门.但如果将其扩展到整个软件开 ...

  4. 浅谈iOS 开发中的界面通信

    在任何的软件开发中都离不开界面与界面之间的通信,界面通信的最直接的方法就是界面传值. 在开发过程中我们在页面传值时我们通常使用的方法有:属性传值法,block传值法,代理传值法,以及单例传值法,通知传 ...

  5. 浅谈Web开发中的6种技术

    CSDN博客不再经常更新,更多优质文章请来 粉丝联盟网 FansUnion.cn! (FansUnion) Web开发中的6种技术 1.html 超文本标记语言,即HTML(Hypertext Mar ...

  6. 浅谈项目开发现状(一)

    在现在的软件开发中,一些大的软件公司有充分的资金,所以他的公司人员组织架构能组成:需求分析团队(为了更好的了解用户的完整需求)--->研发团队(通过计算机语言来实现用户需求),方式如下: 虽然客 ...

  7. 浅谈游戏开发中逻辑与表现的分离

    回顾之前做的几个Demo,做点总结. 一.做法: 为了更清晰,NRatel将 一个游戏对象类 拆分为 两个类,如下: 1.定义"纯粹的逻辑类".   基本职责:对游戏对象的&quo ...

  8. 浅谈敏捷开发中的设计

    敏捷开发在当今业界已经大行其道,想要快速交付,采用敏捷开发方法似乎是最好的方式,是否必须要用这就另当别论了.敏捷开发以用户的需求进化为核心,采用迭代.循序渐进的方法进行软件开发,不过,想要真正做到快速 ...

  9. java怎么设置全局变量_浅谈Java开发中如何定义的全局变量

    Static静态变量 在程序中任何变量或者代码都是在编译时,是由系统自动分配内存来存储的,而所谓静态就是指在编译后分配的内存会一直存在,直到程序退出时才会释放内存空间. static是静态修饰符.被s ...

  10. 浅谈软件开发工具CASE在软件项目开发中发挥的作用认识

    浅谈软件开发工具CASE在软件项目开发中发挥的作用认识 内容摘要:阐述了CASE工具作为 一种开发环境在软件项目开发中所起到的开发及管理作用.CASE工具实际上是把原先由手工完成的开发过程转变为以自动 ...

最新文章

  1. 布道微服务_07服务调用追踪
  2. python numpy.arange() 函数的使用方法 (在给定间隔内返回均匀间隔的值)
  3. POJ - 3415 Common Substrings(长度不小于K的公共子串个数)
  4. LeetCode 394. 字符串解码(栈)
  5. flash 定义主舞台窗口大小
  6. 【Swift学习】Swift编程之旅---析构方法(十九)
  7. fatfs 文件属性_FATFS文件系统剖析(全).
  8. 研磨设计模式学习笔记1--简单工厂(SimpleFactory)
  9. KEIL5下载程序失败解决办法
  10. 第一个hadoop程序(hadoop2.4.0集群+Eclipse环境)
  11. 天地图key申请_国家地理信息公共服务平台 天地图
  12. python视频图片识别算法_python利用Opencv进行人脸识别(视频流+图片)
  13. 如何把操作系统迁移到新电脑/硬盘
  14. 水星USB无线网卡mw150us苹果macOS系统驱动成功
  15. K3ERP web登录问题解决
  16. 新学编程之掌握基本概念
  17. VIPER,更清晰的架构,解决复用和测试问题的利器系列1:VIPER架构演进史
  18. algebraic reconstruction technique (ART)算法
  19. mysql 输出名称_MySQL常用的SQL语句//输出所有信息showfullfieldsfrom'表名称';//改表
  20. 教你如何把视频转成序列图片的实用技巧

热门文章

  1. 几大主流云存储服务深层对比
  2. 情窦初开,第一次学着当一个人的女朋友时,我很狼狈
  3. 让人人都能用得起基因检测:23魔方发布第四代新品
  4. iPhone,iPad型号,屏幕尺寸,分辨率小结
  5. 汉字拼音转换工具(Python版)
  6. PT13-47页蓝色互联网年中晋升竞聘工作汇报PPT模板
  7. 2022年G2电站锅炉司炉考试报名及G2电站锅炉司炉复审考试
  8. 关系型数据库与非关系型数据库的区别汇总
  9. 麦库记事Android客户端(V1.4.3)评测
  10. Sequence Level Training with Recurrent Neural Networks-学习笔记