模組的封裝
以前我轉載過,現在找不到了,從新開篇部落格吧。我們一直在寫程式碼,大部分人都是從底層往上學(只有一些極少數的幸運兒能夠有幸從上往下學習),因此我們沒有機會站在工程的角度或者說產品的角度來看待我們的程式碼。通俗的說,就是我們只對自己的一畝三分地熟悉,而沒有全域性觀。沒有全域性觀,不能站在工程的角度看待問題,也就無所謂封裝了。這篇文章就是封裝的入門知識,讓我們從另外一個高度來看待問題,思考問題。希望能夠幫助大家。。。。。。
廢話這麼多,進入正文:
我們統一管模組不叫模組,而叫service。
一個service一定是儲存在一個資料夾裡面的;service叫什麼名字,資料夾就叫什麼名字。
一個service對外界來說就是一個黑盒子。黑盒子的意思就是說,外界對於service的內部,也就是資料夾裡面的內容是不關心的;黑盒子與外界理論上只通過介面進行溝通。
因此,一個service的資料夾裡面,一定有一個與service同名的.h檔案,我們叫做介面標頭檔案。外界要想使用這個service,只能通過包含這個標頭檔案來實現。
比如,我們有一個service叫做byte_queue;那麼資料夾的名字也叫作byte_queue;在這個資料夾裡面就有一個 byte_queue.h。
這裡,介面標頭檔案是用來從service內部向外部提供介面的。從提供資訊的角度來說是由內而外的。因此我們一般認為,介面標頭檔案是用來“輸出介面”的。
與之相對的,對一個service來說,往往還是要提供一些輸入資訊的。比如,系統的配置(通過巨集來配置程式碼);系統的基礎環境(變數型別之類的);還有這個service可能會依賴的其他service;這類資訊是由外而內的輸入資訊的。我們用一個配置型標頭檔案 app_cfg.h 來承擔這個責任。
大家基本上都遇到過一個系統裡面只有頂層有一個system.h;然後所有.c都包含system.h來獲得便利的模式。即便你以前沒有遇到過,以後也一定會遇到——這就是比較傳統的處理標頭檔案的做法。
這種做法混合了前面所說的兩種標頭檔案的職能——正因為是混合了,所以才導致了很多混亂:
首先,這種混合做法根本沒有模組化的思想。
其次,將輸入和輸出資訊混合在一起,容易造成複雜的包含性錯誤。
所以應該區分資訊的型別,然後放置到正確的標頭檔案中。
比如,晶體配置,硬體連線,明顯屬於配置類或者基礎環境類的資訊。這些資訊應該是輸入性質的,應該寫入配製標頭檔案。
一個service裡面必然擁有一個介面標頭檔案;也必然擁有一個配製標頭檔案。
介面標頭檔案的名字和service名稱一樣;但配製標頭檔案一定叫app_cfg.h(為啥一定呢?因為是前輩們規定的)
模組裡面的app_cfg.h的內容如下:
#include "..\app_cfg.h"
#ifndef __XXX_APP_CFG_H__
#define __XXX_APP_CFG_H__
...
#endif
__XXX_APP_CFG_H__是保護巨集,防止同樣的內容被包含兩次。
請務必注意到這一點:app_cfg.h一定會在保護巨集之前包含上一級目錄的app_cfg.h。
一個service是一個黑盒子,裡面究竟怎麼實現其實是無所謂的;但一定包含一個介面標頭檔案和一個配製標頭檔案。
需要強調一點,對於介面標頭檔案來說,黑盒子裡面的所有的.c和.h都不會去包含它——簡單說就是介面標頭檔案是一個孤立的標頭檔案。
關於配製標頭檔案,模組內所有的檔案都會包含它。
這就是service封裝的基本規則。
思考總結:
1、模組的.c和.h都包含app_cfg.h配置檔案,是為了保持配置的一致性;
2、app_cfg.h要用下面這種形式包含上層模組的配置資訊,是為了使程式
像搭積木一樣,能夠把最上層的配置資訊準備傳遞給下面左右的模組;
#include "..\app_cfg.h"
#ifndef __XXX_APP_CFG_H__
#define __XXX_APP_CFG_H__
...
#endif
3、.c要用下面方式包含本模組的配置資訊,是為了能夠準備的包含的
本模組的配置資訊,而沒有歧義;
#include ".\app_cfg.h"
4、.h用下面的方式包含本模組的配置資訊,是為了使配置資訊僅僅被
呼叫模組包含,而不會被巢狀包含擴散;
#ifndef __XXX_APP_CFG_H__
#define __XXX_APP_CFG_H__
#include ".\app_cfg.h"
...
#endif