【说在前面的话】
在工程开发中进行模块化的本来目的——为了复用已有的代码,节省当前项目的开发时间;
实际操作过程中遇到的尴尬问题——模块的具体实现原本应该被视作黑盒子,程序员因为各种心理上的原因要阅读代码;
以及
“原则上”的解决方案——严禁程序员在项目开发过程中阅读模块的具体实现代码。
【正文】

其次,每一个模块中都有一个专门的头文件,用于提供给模块的使用者来包含(#include);该头文件的名称必须与模块的名称相同。

需要特别强调和说明的是:
该头文件用于“从模块内部向模块外部”提供使用模块所必须的“最小信息”;
任何人要使用模块,必须且只能包含该头文件;
我们把这类向模块的使用者提供必要信息的头文件称之为接口头文件;
接口头文件遵循“最小信息公开原则”,即,该头文件中只存放用户使用模块最少最少所必须知道的信息。实际操作中,类型定义、宏定义、函数和全局变量声明都应该首先放置在对应的源代码中(或是后面会提到的模块内私有的接口头文件中);当且仅当我们发现用户要使用模块的某一功能必须要用到某一信息时,才“极不情愿”地、“抠门”的、且尽可能将其它能剥离和隐藏的信息剥离开后,放置到接口头文件中。
该头文件用于“从模块外部向模块内部”输入配置信息;
如无特殊说明或安排,该头文件应该固定命名为 app_cfg.h (没有额外的前缀和后缀);
如无特殊说明或安排,该头文件应该仅包含配置信息,例如:宏定义、类型定义(在极其特殊的情况下,偶尔出现的全局变量或者函数声明);
我们把这类头文件称之为“配置头文件”;

为了实现这一点,一个模块内部 app_cfg.h 的固定内容格式为:
//! 作为模块的用户,不要修改这里的任何内容#include "../app_cfg.h"/* app_cfg.h 的防重复包含的保护宏 *//* 请将 XXXXXX 替换为模块的名称,并删除本注释 */#ifndef __XXXXXX_APP_CFG_H__#define __XXXXXX_APP_CFG_H__...#endif /* app_cfg.h 文件的结尾 */
一个模块的接口头文件,其内部格式可能为:
//! 作为模块的用户,不要修改这里的任何内容/* 模块接口头文件防重复包含的保护宏 *//* 请将 XXXXXX 替换为模块的名称,并删除本注释 */#ifndef __XXXXXX_H__#define __XXXXXX_H__/* 模块的接口头文件在一开始要包含当前模块的 app_cfg.h,* 这里的 "./" 不可以省略*/#include "./app_cfg.h"/* 其它include */...#endif /* 接口头文件的结尾 */
可以很容易注意到,当使用某一模块时,用户可以很方便的在模块外部定义一个属于自己的 app_cfg.h 来向模块提供配置信息——而无论如何修改这一文件,都不会破坏黑盒子本身的内容。
再次,一个模块往往拥有一个或多个C源文件,它只需要包含模块的接口头文件,就可以共享一些“对外公开的信息”。

这里有个朋友会问了:根据最小信息公开原则,接口头文件中只包含了一些最小信息,如果模块内的多个C源文件之间需要共享一些非公开的私有信息,该怎么处理呢?

一个典型的 __common.h 内容如下:
/*! 作为模块的用户,不要修改这里的任何内容,理论上也不应该关心这* 里出现的任何内容。* 对模块的作者来说,如果模块以 lib 的形式提供,请务必将本文件删除*/#ifndef __XXXXXX_COMMON_H__#define __XXXXXX_COMMON_H__...#endif /* 私有接口头文件的结尾 */
基于这一规则,模块内一个可能的C源文件内容如下:
//! 作为模块的用户,不要修改这里的任何内容/* 首先包含模块的接口头文件,模块的配置头文件也会间接的被引入进来 */#include "./xxxxx.h"#include "./__common.h"/* 当前C源文件私有且不想跟模块内其它C文件共享的内容: 宏、类型定义等等 */.../* 函数实现等等 */...
最后,一个模块内是允许包含其它子模块的,对于这种嵌套情况,仅需要两步骤就可以完成部署:
将子模块拷贝到父模块中,或者按照前述的模块构建规则,在父模块中建立一个子模块;
父模块的接口头文件包含子模块的接口头文件;
少数情况下,如果子模块与父模块高度耦合(一般来说就是在父模块中从头开始建立一个新的子模块时会发生这种情况)——比如子模块依赖父模块的 __common.h 中提供的信息,则应该在子模块中也建立一个 __common.h,并仿照 app_cfg.h 的做法,在头文件的一开始首先向上包含父模块的 __common.h;
如果父模块包含__common.h,而子模块并不需要这一信息,则子模块无需在做任何特殊修改。
对app_cfg.h来说,由于子模块原本就会自动包含上一级的app_cfg.h,因此,我们无须做任何特殊操作,子模块就可以透过父模块的app_cfg.h自动从外界获取配置信息——这就像是一种标准化的水管安装。

【后记】
只需要拷贝模块目录就可以完成部署;
只需要在模块的外部额外添加一个app_cfg.h就可以实现对模块的配置;
所有关于模块的使用信息(使用说明书)都放置在一个唯一的、与模块同名的接口头文件中;且这里包含的信息对用户来说都是可用的(没有无用信息,也没有多余信息);
对模块的开发者来说:
这一模型是高度遵守黑盒子原则的;
用户使用模块,是不需要“用脏手染指”自己宝贵的代码的(无需修改);
对制作 Library 非常友好,只需要保留接口头文件,而将其它所有文件(包括源代码和私有接口头文件)删除并保留一个固化好的app_cfg.h即可。
模块是非常容易迁移和嵌套的。
当然,这一Service模型也有一个小缺点(可能有些人也对此无法容忍),即,用某些工程管理工具将头文件的包含关系展开时,通常会看到海量的app_cfg.h(尽管他们内部都使用了模块特有的保护宏进行区别)——对于这一问题,在真刀真枪模块化的后续内容中,将提供一个较为完美的解决方案,这里就先卖个关子——对普通用户来说,现有的Service模型足够了。
扫描下方微信,加作者微信进技术交流群,请先自我介绍喔。
推荐阅读:
嵌入式编程专辑 Linux 学习专辑 C/C++编程专辑 Qt进阶学习专辑

如果你喜欢我的思维,欢迎订阅 裸机思维

