程序的抽象

2020-03-08 19:21发布

大家都知道,很早之前我们中国就发现了“勾三股四腰五”的现象,但也仅仅停留在这里,因为当两条直角边时不是3,4时,斜边长度与两条直角边之间有什么关系当时就不得而知了。真正解决直角三角形三边关系的人是古希腊数学家毕达哥拉斯,我们把他的发现称为毕达哥拉斯定理。通过这个定理,我们就可以根据任意两边的长度计算第三边的长度,而不像我们只发现了一个特例。
毕达哥拉斯把变化的边长分离出去(两条直角边为a、b,斜边为c,而不是具体的数字),找到了直角三角形共有的特性,从而得出一个通用的公式:a^2 + b^2 = c^2 。这个过程就是一个典型的抽象过程---分离出变化的因素,找到共有特性
抽象,除了让数学具有通用性以外,也能帮助的我们设计程序,使得我们的程序更具有通用性,方便了架构程序的同时,还能让我们的工作效率变得更高,因为代码能容易的复用到其他项目中。抽象能带来很多好处,那么,如何抽象一个程序呢。
在讲具体的例子之前,我先说说我在程序抽象时使用的基本原则,这个原则就像数学定理一样,他能指导我设计程序。这个原则主要有两点:
  • 分离出某个功能的变化部分,找到这个功能的共同点
  • 问自己一个问题,我实现的这个模块是否能容易的复用到其他项目中去

要做到这两点并不容易,特别在编程之前就能对功能做很好的抽象就更难了,因此要做到上述两点,除了要在编程前做一个分析以外,还需要在编程的过程中以及以后做代码维护时不断的重构代码才可能渐渐达到上述的要求。
下面我就以具体的例子来说明如何抽象程序,我先描述一下我们要解决的问题。
控制蜂鸣器实现提示音功能(图1)。要求如下:状态1时短响提示1声,状态2时短响提示2声,状态3时短响提示3声,另外,假设蜂鸣器一声响声的持续时长为100ms,两声之间的间隔100ms,如图(2)所示。
clipboard.png
               图一

clipboard111.png
              图二对于这个问题,要解决它并不难,但是做为一个说明程序如何抽象的例子却是不错的。
蜂鸣器的提示有3种状态,要现实这个功能,我们当然能分别编写3个独立的控制函数分别控制蜂鸣器响1、2、3声。如果真的这么做了除了造成蜂鸣器功能管理上的麻烦(因为你需要协调3个函数控制一个蜂鸣器),还有一个就是程序复用性不好,如果我还需要一个3声的提示音,那是不是还要再编写一个控制函数呢。显而易见这种做法虽然能实现但是并不可取。
仔细看看问题,这些提示音之间的区别仅仅在于短响提示的次数不一样,控制蜂鸣器的逻辑其实都是相同的。因此,基于这个特征,我们抽象出相同的逻辑控制部分,把变化的提示次数独立出来,这样就能保证程序在一定程度上的复用性。具体的做法如下。
  1. //以下代码放在buz.c中
  2. #include "buz.h"

  3. typedef
  4. {
  5.   BUZ_ON,
  6.   BUZ_OFF
  7. }buz_st_t;

  8. struct
  9. {
  10.   buz_size_t  cnt;
  11.   buz_st_t    status;
  12. }buz;

  13. //100ms的定时器
  14. void app_buz_clk_100ms(void )
  15. {
  16.   app_buzzer_manage();
  17. }

  18. //蜂鸣器提示设置,cnt为要提示的次数
  19. void app_buzzer_prompt_set(buz_size_t cnt)
  20. {
  21.   buz.cnt = cnt;
  22.   buz.status = BUZ_ON;
  23. }

  24. //蜂鸣器的管理函数,在app_clk_100ms中每间隔100ms调用
  25. void app_buzzer_manage(void )
  26. {
  27.   if(buz.cnt == 0)
  28.   {
  29.         return;  
  30.   }         
  31.   
  32.   if(buz.status == BUZ_ON)
  33.   {
  34.         BUZ_CTR_H; //控制蜂鸣器的引脚拉高
  35.         buz.status = BUZ_OFF;
  36.   }
  37.   else
  38.   {
  39.         buz.cnt--;
  40.         
  41.         BUZ_CTR_L; //控制蜂鸣器的引脚拉低
  42.         buz.status = BUZ_ON;
  43.   }
  44. }

  45. //以下代码放在buz.h中
  46. #ifndef _APP_BUZ_
  47. #define _APP_BUZ_

  48. typedef buz_size_t unsigned char; //方便后面更改类型

  49. extern void app_buzzer_prompt_set(buz_size_t  );
  50. extern void app_buz_clk_100ms(void );

  51. #endif
复制代码

从上面的代码示例可以看出,把相同的逻辑功能抽象出来,并把变化部分分离出去后,应用者愿意响几声就响几声,而且,这个程序也能容易的被复用到其他项目中去,这样程序的复用性就增加了。
假如现在对蜂鸣器的功能再增加一个要求,允许应用者修改蜂鸣器一声响声的时长以及两声之间的间隔,以便更能满足实际需要,这个时候怎么办呢。
很明显,把具体的响几声,以及响声的持续时长、响声之间的时间间隔分离,抽象出他们相同部分---逻辑功能。因此,按照这个新要求,对代码做如下修改:
  1. //以下代码放在buz.c中
  2. struct
  3. {
  4.   buz_size_t cnt;
  5.   buz_size_t on_time;
  6.   buz_size_t off_time;
  7.   buz_size_t buz_interval_time;
  8.   
  9.   buz_st_t status;
  10. }buz;

  11. //增加了对外接口
  12. //on_time表示蜂鸣器的一声响声的持续时间
  13. //off_time表示蜂鸣器两声之间的时间间隔
  14. void app_buzzer_prompt_set(buz_size_t on_time, buz_size_t off_time, buz_size_t cnt)
  15. {
  16.   buz.cnt = cnt;
  17.   buz.on_time = on_time;
  18.   buz.off_time = off_time;
  19.   
  20.   buz.interval_time = 0;
  21.   buz.status = BUZ_ON;
  22. }

  23. void app_buzzer_manage(void )
  24. {
  25.   if(buz.cnt == 0)
  26.   {
  27.         return;  
  28.   }         
  29.   
  30.   if(buz.interval_time)
  31.   {
  32.         buz.interval_time--;
  33.         return;        
  34.   }
  35.   
  36.   if(buz.status == BUZ_ON)
  37.   {
  38.         BUZ_CTR_H; //控制蜂鸣器的引脚拉高
  39.         buz.status = BUZ_OFF;
  40.         buz.interval_time = buz.on_time;
  41.   }
  42.   else
  43.   {
  44.         buz_cnt--;
  45.         
  46.         BUZ_CTR_L; //控制蜂鸣器的引脚拉低
  47.         buz.status = BUZ_ON;
  48.         buz.interval_time = buz.off_time;
  49.   }
  50. }
复制代码

可以看到,蜂鸣器的功能除了能任意指定响几声外,还能控制蜂鸣器的响声持续时间以及两声响声的间隔时间,这时,蜂鸣器的控制模块的功能就变得更为强大,复用性更好。但是不要高兴太早,凡事有利有弊,你可能也注意到了,随着程序复用性的提高,程序除了变得更为复杂以外,还消耗了更多的资源,比如单片机的RAM,当然这对于一些高性能单片机来说不是问题,对于一些资源紧张的单片机来说有时可能就有问题了。
因此,复用性虽好,但是过分追求程序的复用性,有时候反而有相反的作用,过犹不及还是很有道理的。到底该做怎么样的选择,还是那句老话,你需要根据实际情况决定。
这一篇文章说了如何抽象一个程序,目的是为了提高我们编写程序的复用性,以帮助我们更好有效率的应对工作。除此之外,抽象还有一个作用就是方便程序的架构,比如后面要讲到的三层架构,他的中间层就是对应用层抽象的结果,这样既能保证中间层的复用性,同时应用层只需要专注于利用中间层提供的功能完成目标设计,降低应用层的复杂度,以设计出更稳定的程序。




友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。