軟件項目各有不同,開發語言多種多樣,但軟件開發這種行為過程,有其規律性,很多前輩從各個角度對軟件開發這種行為做了總結。我們這里來介紹GOF從工程實現的角度總結的23種設計模式(最近實踐),這將會是一個系列。
軟件開發是一種智力活動,溝通交流時多有障礙,從設計模式的角度來溝通功能的實現,也能大大提高溝通效率。
在我個人的軟件開發經歷中,感受最深的是命令模式和觀察者模式。我們先從命令模式開始。
先來看一個通訊協議相關的例子,數據幀定義如下:
幀長度 | 傳感器類型 | 數據 |
---|---|---|
1字節 | 1字節 | n字節 |
傳感器數據格式定義如下
傳感器 | 類型號 | 數據格式 |
---|---|---|
溫度傳感器 | 1 | 整型數據(4字節) |
濕度傳感器 | 2 | 整型數據(4字節) |
解析實現可如下實現:
static int parse(const char *data,size_t n)
{
int len = data[0];
int type = data[2];
int value;
switch(type){
case 1:
value = *(int *)&data[3];
printf("temperatue = %d\n",value);
case 2:
value = *(int *)&data[3];
printf("humidity = %d\n",value);
default:
printf("parse error\n");
}
}
相信你已經嗅到了這段代碼的壞味道,如果我們系統中要添加更多的傳感器類型,勢必需要向switch case
語句中增加更多的case
語句。使得parse
函數越來越臃腫。
當然,我們可以采用,如下方式規避一些問題
static int parse_temperature(const char *data,size_t len)
{
assert(len == 4);
int value = *(int *)data;
printf("temperature = %d\n",value);
}
static int parse_humidity(const char *data,size_t len)
{
assert(len==4);
int value = *(int *)data;
printf("humidity = %d\n",value);
}
static void parse(const char *data,size_t len)
{
int data_len = data[0];
int data_type = data[1];
switch(type){
case 1:
parse_temperature(data+2,data_len-2);
break;
case 2:
parse_humidity(data+2,data_len-2);
break;
default:
break;
}
}
上面的代碼相比與第一次實現有所改善,但是我們采用命令模式實現后,會看到,無論是可讀性,可擴展性,都會得到相當成都的提高。
typedef int (*parse_func)(const char *data,size_t len);
struct parse_handler{
int type;
parse_func func;
}
static int parse_temperature(const char *data,size_t len)
{
assert(len == 4);
int value = *(int *)data;
printf("temperature = %d\n",value);
}
static int parse_humidity(const char *data,size_t len)
{
assert(len==4);
int value = *(int *)data;
printf("humidity = %d\n",value);
}
static struct parse_handler handlers = {
{1,parse_tmepeature},
{2,parse_humidity},
};
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
static parse_func parse_func_find(int type)
{
int i;
for(i=0;i<ARRAY_SIZE(handlers);i++){
if(handlers[i].type == type)
return handlers[i].func;
}
return NULL;
}
static int parse(const char *data,size_t len)
{
int data_len = data[0];
int data_type = data[1];
parse_func func = parse_func_find(data_type);
if(func != NULL)
func(data+2,data_len-2);
}
如上,需要增加或者修改傳感器的時候,我們只需要關注handlers
這個數組。
static struct parse_handler handlers = {
{1,parse_tmepeature},
{2,parse_humidity},
};
這樣寫的優勢
- 傳感器類型和解析實現直接寫在了同一行,非常直觀,也沒有必要將傳感器類型數據定義為宏。
- 添加傳感器的時候,只要在結構體數組中添加一行即可,如果是第二種實現,則需要在
parse
函數中,添加一個case
語句,若是遺忘了break
很容易導致錯誤。
這種實現方式,基本是此類問題的最優解。被稱為命令模式。一個type被稱為一個命令,解析函數對應于命令的實現。
很多命令行工具都是采用的此類實現方式。偽代碼如下:
char buffer[1024];
while(true){
char *p = fgets(buffer,sizeof(buffer),stdin);
execute(p) //執行某個命令,先find再執行。
}