單一職責原則:通常的定義是只專注於做一件事和僅有乙個引起它變化的原因。對於介面、實現、函式級別往往我們比較容易關注單一職責,大家談的也比較多,但對於返回值、引數可能不會有太多的人關注。但往往就是這些不符合單一職責原則的設計可能導致一些很難發現的bug。看看下面這段**:
pbuf = (byte*)realloc( pbuf, size);
if( pbbuf != null )
可能很多人一眼看上去並沒有什麼問題,先讓我們看看這個庫函式的定義:
extern void *realloc(void *mem_address, unsigned int newsize);
語法:指標名=(資料型別*)realloc(要改變記憶體大小的指標名,新的大小)。
extern flag realloc(void **ppmem_address, unsigned int newsize);
相信任何乙個使用這個介面的人都會寫出下面的**:
if( true == realloc( &pbuf, size))
else
為什麼有人會犯pbuf = (byte*)realloc( pbuf, size);
這種錯誤?因為他只關注了realloc
返回值是乙個位址,沒有關注該返回值還有錯誤識別的功能,換句話來說這個庫函式的返回值不具備單一職責,導致了可能的錯誤使用。如果使用改進後的介面,因為返回值只有乙個判斷分配成功與否的功能,相信沒有人還會用錯。
我們再仔細看看我們新的介面,總覺得似乎有什麼地方還是不對,看到void **ppmem_address
可能要想一下明白,這個引數既是入參又是出參,它承擔了原始位址的輸入和新位址的輸出,這不又違反了單一職責嗎?好吧我們再改進一下:
現在這個介面就算乙個初次看到的人也應該大概知道什麼意思,相信也不會寫出什麼帶bug的**,因為函式的引數、返回值都具有單一的功能,通過返回值來判斷分配成功與否,通過出參來獲取位址。一切看起來都很清晰。
在c庫中還有很多類似的函式,如果當初的設計人員能多考慮單一職責,也許現在的系統中就會少了很多隱藏的bug,介面永遠是給別人使用的,一定要把使用者當成傻瓜,也許才能設計出好的介面。
為什麼要用c來模擬物件導向的機制,在實際的工作中我們往往在感慨一些物件導向的經典設計模式由於c語言的限制無法使用,其實通過簡單的模擬物件導向的行為,在c語言中也可以使用這些模式。
類描述了所建立的物件共同的屬性和方法。我們在乙個原始檔中通過把資料和操作進行適當的組織來完成類的模擬。
/*類的資料*/
typedef struct square_s square_t;
struct square_s
;/*類的方法*/
static void draw(void* pobj)
如上所示,乙個正方形的類我們用乙個結構體square_t來表示正方形的屬性,draw是其中的乙個方法。
類的封裝一般要求對細節的隱藏並且提供指定的方法供呼叫者使用,在square這個類中,sidelen是圖形的細節,只需要提供乙個draw介面給呼叫者。因此在提供給外部呼叫的介面標頭檔案中構建如下的介面。
typedef struct shape_s shape;
struct shape_s
;
通過定義不同的資料結構來達到資料隱藏的目的,如下圖所示,對外介面中只能看到draw,內部實現中可以看到draw和sidelen。
多型無疑是物件導向語言的很重要的乙個機制,很多物件導向的設計模式都是以多型為基礎,c語言並不支援多型,導致很多設計模式都無法直接使用。
乙個典型的多型例子,通過宣告乙個shape介面,根據例項化物件型別的不同,pshape在執行時動態的表現不同的行為。
shape* pshape = null; //乙個形狀介面
pshape = (shape*)ins(square,2); //例項化為乙個正方形
pshape->draw(pshape); //pshape表現為正方形的行為
/*每個類的建構函式*/
static void* constructor(void* pobj,va_list* pdata)
有了類,我們需要例項化為可以執行的物件,例項化主要的工作是分配記憶體、動態繫結、資料初始化等工作。
void* ins(const void* pclass,...)
定義為客戶端不應該依賴它不需用的介面,在c語言中我們可以把頭檔案看成乙個模組的介面,根據介面隔離原則也就是說這個標頭檔案中只能包含外部需要的介面,但在實際的專案中往往標頭檔案都不符合介面隔離原則。
標頭檔案中通常包含了模組內部介面(內部型別定義、內部介面宣告)和外部介面(外部介面宣告)
假設moudle模組對外提供乙個fun1介面,模組內部實現需要定義乙個結構型別,一般的實現如下:
/*moudle.h*/
typedef struct str_s str_t;
struct str_s
;void fun1();
/*moudle.c*/
#include "moudle.h"
void fun1()
;todo...
}
客戶端在使用介面的時候需要包含moudle.**件,而該介面並不符合介面的隔離,其內部包含了客戶並不需要的一些定義。為了解決這個問題我們可以通過定義不同的標頭檔案來隔離介面,moudle.h定義外部的介面,moudle.inc定義內部介面
/*moudle.h*/
void fun1();
/*moudle.inc*/
typedef struct str_s str_t;
struct str_s
;/*moudle.c*/
#include "moudle.inc"
void fun1()
;todo...
};
moudle.h包含外部模組需要的介面,外部模組包含moudle.h,moudle.inc包含內部模組需要的介面,在模組內部包含moudle.inc。通過檢視模組的.inc和.**件,我們就可以清晰的理解模組對外和對內提供了什麼介面。
在實際專案中我們經常可以看到一些標頭檔案包含了所有模組的介面宣告,客戶端只需要包含這個標頭檔案就可以使用任何介面了。
/*global.h*/
#inlcude "moudle1.h"
#inlcude "moudle2.h"
#inlcude "moudle3.h"
....
#inlcude "moudlen.h"
可能帶來如下問題:
會顯著的增加編譯時間,如果專案大,可能大部分的編譯時間都花在展開標頭檔案(筆者乙個專案測試80%左右的時間)。
不利於**的框架的理解,客戶端無法從包含的標頭檔案中清晰的看到依賴什麼外部模組。
乙個資料獲取模組提供兩個介面分別從網路和本地快取獲取資料,後台管理模組使用網路介面定時獲取資料更新快取,前台模組使用快取介面快速獲取資料顯示,由於沒有對介面隔離,後期的維護人員可能並不清楚開始的設計,在前台模組中直接使用網路介面來獲取資料顯示,導致介面延遲嚴重。如果一開始就把介面分離,給前台模組提供本地快取介面,給後台模組提供網路介面,就不會導致問題的出現。
嵌入式系統軟體架構設計(長篇深度好文)
分享乙個非常有用且簡單c語言測試框架
分享乙個自己量產專案上的整合測試軟體mttest
使您的軟體執行起來: 防止緩衝區溢位(c語言精華帖)
若覺得本次分享的文章對您有幫助,隨手點[在看]
並**分享,也是對我的支援。
C 萬能標頭檔案
萬能標頭檔案其實就一行 include其實它包括了 基本包括了大部分的stl庫函式,使用方便快捷 include include include include include include include 向量 include 佇列 include include include include...
C 萬能標頭檔案使用
include包含了目前c 所包含的所有標頭檔案,而且連c的標頭檔案也不需要寫了!現在再看下面這一堆亂七八糟的標頭檔案顯得莫名的冗雜 1 include 2 include 3 include 4 include 5 include 6 include 7 include 8 include 9 i...
C語言小筆記 萬能排序
include include include typedef struct stustu 函式功能 排序 萬能排序 形參列表 void base 資料起始位置 size t nmemb 資料的個數 size t size 每個元素的位元組大小 int compare const void cons...