嵌入式編程模塊化——6張圖來解析實用型Service模型
【說在前面的話】
在工程開發(fā)中進行模塊化的本來目的——為了復(fù)用已有的代碼,節(jié)省當(dāng)前項目的開發(fā)時間;
實際操作過程中遇到的尷尬問題——模塊的具體實現(xiàn)原本應(yīng)該被視作黑盒子,程序員因為各種心理上的原因要閱讀代碼;
以及
“原則上”的解決方案——嚴禁程序員在項目開發(fā)過程中閱讀模塊的具體實現(xiàn)代碼。
【正文】

其次,每一個模塊中都有一個專門的頭文件,用于提供給模塊的使用者來包含(#include);該頭文件的名稱必須與模塊的名稱相同。

需要特別強調(diào)和說明的是:
該頭文件用于“從模塊內(nèi)部向模塊外部”提供使用模塊所必須的“最小信息”;
任何人要使用模塊,必須且只能包含該頭文件;
我們把這類向模塊的使用者提供必要信息的頭文件稱之為接口頭文件;
接口頭文件遵循“最小信息公開原則”,即,該頭文件中只存放用戶使用模塊最少最少所必須知道的信息。實際操作中,類型定義、宏定義、函數(shù)和全局變量聲明都應(yīng)該首先放置在對應(yīng)的源代碼中(或是后面會提到的模塊內(nèi)私有的接口頭文件中);當(dāng)且僅當(dāng)我們發(fā)現(xiàn)用戶要使用模塊的某一功能必須要用到某一信息時,才“極不情愿”地、“摳門”的、且盡可能將其它能剝離和隱藏的信息剝離開后,放置到接口頭文件中。
該頭文件用于“從模塊外部向模塊內(nèi)部”輸入配置信息;
如無特殊說明或安排,該頭文件應(yīng)該固定命名為 app_cfg.h?(沒有額外的前綴和后綴);
如無特殊說明或安排,該頭文件應(yīng)該僅包含配置信息,例如:宏定義、類型定義(在極其特殊的情況下,偶爾出現(xiàn)的全局變量或者函數(shù)聲明);
我們把這類頭文件稱之為“配置頭文件”;

為了實現(xiàn)這一點,一個模塊內(nèi)部 app_cfg.h 的固定內(nèi)容格式為:
//!?作為模塊的用戶,不要修改這里的任何內(nèi)容#include "../app_cfg.h"/*?app_cfg.h?的防重復(fù)包含的保護宏 *//*?請將?XXXXXX?替換為模塊的名稱,并刪除本注釋?*/#ifndef?__XXXXXX_APP_CFG_H__#define?__XXXXXX_APP_CFG_H__...#endif???/*?app_cfg.h?文件的結(jié)尾?*/
一個模塊的接口頭文件,其內(nèi)部格式可能為:
//!?作為模塊的用戶,不要修改這里的任何內(nèi)容/*?模塊接口頭文件防重復(fù)包含的保護宏?*//* 請將 XXXXXX 替換為模塊的名稱,并刪除本注釋 */#ifndef __XXXXXX_H__#define __XXXXXX_H__/*?模塊的接口頭文件在一開始要包含當(dāng)前模塊的?app_cfg.h,?*?這里的?"./"?不可以省略??*/#include?"./app_cfg.h"/* 其它include */...#endif???/*?接口頭文件的結(jié)尾?*/
可以很容易注意到,當(dāng)使用某一模塊時,用戶可以很方便的在模塊外部定義一個屬于自己的 app_cfg.h 來向模塊提供配置信息——而無論如何修改這一文件,都不會破壞黑盒子本身的內(nèi)容。
再次,一個模塊往往擁有一個或多個C源文件,它只需要包含模塊的接口頭文件,就可以共享一些“對外公開的信息”。

這里有個朋友會問了:根據(jù)最小信息公開原則,接口頭文件中只包含了一些最小信息,如果模塊內(nèi)的多個C源文件之間需要共享一些非公開的私有信息,該怎么處理呢?

一個典型的 __common.h 內(nèi)容如下:
/*!?作為模塊的用戶,不要修改這里的任何內(nèi)容,理論上也不應(yīng)該關(guān)心這?*?里出現(xiàn)的任何內(nèi)容。* 對模塊的作者來說,如果模塊以 lib 的形式提供,請務(wù)必將本文件刪除?*/#ifndef?__XXXXXX_COMMON_H__#define __XXXXXX_COMMON_H__...#endif /* 私有接口頭文件的結(jié)尾 */
基于這一規(guī)則,模塊內(nèi)一個可能的C源文件內(nèi)容如下:
//! 作為模塊的用戶,不要修改這里的任何內(nèi)容/*?首先包含模塊的接口頭文件,模塊的配置頭文件也會間接的被引入進來?*/#include?"./xxxxx.h"???????????#include?"./__common.h"/*?當(dāng)前C源文件私有且不想跟模塊內(nèi)其它C文件共享的內(nèi)容:?宏、類型定義等等 */.../*?函數(shù)實現(xiàn)等等 */...
最后,一個模塊內(nèi)是允許包含其它子模塊的,對于這種嵌套情況,僅需要兩步驟就可以完成部署:
將子模塊拷貝到父模塊中,或者按照前述的模塊構(gòu)建規(guī)則,在父模塊中建立一個子模塊;
父模塊的接口頭文件包含子模塊的接口頭文件;
少數(shù)情況下,如果子模塊與父模塊高度耦合(一般來說就是在父模塊中從頭開始建立一個新的子模塊時會發(fā)生這種情況)——比如子模塊依賴父模塊的 __common.h 中提供的信息,則應(yīng)該在子模塊中也建立一個 __common.h,并仿照 app_cfg.h 的做法,在頭文件的一開始首先向上包含父模塊的 __common.h;
如果父模塊包含__common.h,而子模塊并不需要這一信息,則子模塊無需在做任何特殊修改。
對app_cfg.h來說,由于子模塊原本就會自動包含上一級的app_cfg.h,因此,我們無須做任何特殊操作,子模塊就可以透過父模塊的app_cfg.h自動從外界獲取配置信息——這就像是一種標(biāo)準(zhǔn)化的水管安裝。

【后記】
只需要拷貝模塊目錄就可以完成部署;
只需要在模塊的外部額外添加一個app_cfg.h就可以實現(xiàn)對模塊的配置;
所有關(guān)于模塊的使用信息(使用說明書)都放置在一個唯一的、與模塊同名的接口頭文件中;且這里包含的信息對用戶來說都是可用的(沒有無用信息,也沒有多余信息);
對模塊的開發(fā)者來說:
這一模型是高度遵守黑盒子原則的;
用戶使用模塊,是不需要“用臟手染指”自己寶貴的代碼的(無需修改);
對制作 Library 非常友好,只需要保留接口頭文件,而將其它所有文件(包括源代碼和私有接口頭文件)刪除并保留一個固化好的app_cfg.h即可。
模塊是非常容易遷移和嵌套的。
當(dāng)然,這一Service模型也有一個小缺點(可能有些人也對此無法容忍),即,用某些工程管理工具將頭文件的包含關(guān)系展開時,通常會看到海量的app_cfg.h(盡管他們內(nèi)部都使用了模塊特有的保護宏進行區(qū)別)——對于這一問題,在真刀真槍模塊化的后續(xù)內(nèi)容中,將提供一個較為完美的解決方案,這里就先賣個關(guān)子——對普通用戶來說,現(xiàn)有的Service模型足夠了。
掃描下方微信,加作者微信進技術(shù)交流群,請先自我介紹喔。 推薦閱讀:
嵌入式編程專輯 Linux 學(xué)習(xí)專輯 C/C++編程專輯 Qt進階學(xué)習(xí)專輯
如果你喜歡我的思維,歡迎訂閱 裸機思維

