轉(zhuǎn)載:http://tec.5lulu.com/detail/108asn4wm11y68sdc.html
目錄
數(shù)據(jù)驅(qū)動編程的核心
隱含在背后的思想?
數(shù)據(jù)驅(qū)動編程可以用來做什么?
復(fù)雜一點的表驅(qū)動?
繼承與組合
1堵第、數(shù)據(jù)驅(qū)動編程的核心
數(shù)據(jù)驅(qū)動編程的核心出發(fā)點是相對于程序邏輯,人類更擅長于處理數(shù)據(jù)。數(shù)據(jù)比程序邏輯更容易駕馭,所以我們應(yīng)該盡可能的將設(shè)計的復(fù)雜度從程序代碼轉(zhuǎn)移至數(shù)據(jù)。
真的是這樣嗎免姿?讓我們來看一個示例。
假設(shè)有一個程序,需要處理其他程序發(fā)送的消息轴咱,消息類型是字符串,每個消息都需要一個函數(shù)進行處理。第一印象朴肺,我們可能會這樣處理:
void msg_proc(const char *msg_type, const char *msg_buf)
{
? ? if (0 == strcmp(msg_type, "inivite"))
? ? {
? ? ? ? inivite_fun(msg_buf);
? ? }
? ? else if (0 == strcmp(msg_type, "tring_100"))
? ? {
? ? ? ? tring_fun(msg_buf);
? ? }
? ? else if (0 == strcmp(msg_type, "ring_180"))
? ? {
? ? ? ? ring_180_fun(msg_buf);
? ? }
? ? else if (0 == strcmp(msg_type, "ring_181"))
? ? {
? ? ? ? ring_181_fun(msg_buf);
? ? }
? ? else if (0 == strcmp(msg_type, "ring_182"))
? ? {
? ? ? ? ring_182_fun(msg_buf);
? ? }
? ? else if (0 == strcmp(msg_type, "ring_183"))
? ? {
? ? ? ? ring_183_fun(msg_buf);
? ? }
? ? else if (0 == strcmp(msg_type, "ok_200"))
? ? {
? ? ? ? ok_200_fun(msg_buf);
? ? }
? ? 窖剑。。戈稿。西土。。鞍盗。
? ? else if (0 == strcmp(msg_type, "fail_486"))
? ? {
? ? ? ? fail_486_fun(msg_buf);
? ? }
? ? else
? ? {
? ? ? ? log("未識別的消息類型%sn", msg_type);
? ? }
}
上面的消息類型取自sip協(xié)議(不完全相同需了,sip協(xié)議借鑒了http協(xié)議),消息類型可能還會增加般甲±哒В看著常常的流程可能有點累,檢測一下中間某個消息有沒有處理也比較費勁敷存,而且墓造,沒增加一個消息,就要增加一個流程分支历帚。
按照數(shù)據(jù)驅(qū)動編程的思路滔岳,可能會這樣設(shè)計: ?
typedef void (*SIP_MSG_FUN)(const char *);
typedef struct __msg_fun_st
{
? ? const char *msg_type;//消息類型
? ? SIP_MSG_FUN fun_ptr;//函數(shù)指針
}msg_fun_st;
msg_fun_st msg_flow[] =
{
? ? ? ? {"inivite", inivite_fun},
? ? ? ? {"tring_100", tring_fun},
? ? ? ? {"ring_180", ring_180_fun},
? ? ? ? {"ring_181", ring_181_fun},
? ? ? ? {"ring_182", ring_182_fun},
? ? ? ? {"ring_183", ring_183_fun},
? ? ? ? {"ok_200", ok_200_fun},
? ? ? ? 。挽牢。谱煤。。禽拔。刘离。
? ? ? ? {"fail_486", fail_486_fun}
};
void msg_proc(const char *msg_type, const char *msg_buf)
{
? ? int type_num = sizeof(msg_flow) / sizeof(msg_fun_st);
? ? int i = 0;
? ? for (i = 0; i < type_num; i++)
? ? {
? ? ? ? if (0 == strcmp(msg_flow[i].msg_type, msg_type))
? ? ? ? {
? ? ? ? ? ? msg_flow[i].fun_ptr(msg_buf);
? ? ? ? ? ? return ;
? ? ? ? }
? ? }
? ? log("未識別的消息類型%sn", msg_type);
}
下面這種思路的優(yōu)勢:
1、可讀性更強睹栖,消息處理流程一目了然硫惕。
2、更容易修改野来,要增加新的消息恼除,只要修改數(shù)據(jù)即可,不需要修改流程曼氛。
3豁辉、重用,第一種方案的很多的else if其實只是消息類型和處理函數(shù)不同舀患,但是邏輯是一樣的徽级。下面的這種方案就是將這種相同的邏輯提取出來,而把容易發(fā)生變化的部分提到外面聊浅。
2餐抢、隱含在背后的思想?
很多設(shè)計思路背后的原理其實都是相通的现使,隱含在數(shù)據(jù)驅(qū)動編程背后的實現(xiàn)思想包括:
1、控制復(fù)雜度旷痕。通過把程序邏輯的復(fù)雜度轉(zhuǎn)移到人類更容易處理的數(shù)據(jù)中來碳锈,從而達到控制復(fù)雜度的目標。
2苦蒿、隔離變化殴胧。像上面的例子,每個消息處理的邏輯是不變的佩迟,但是消息可能是變化的,那就把容易變化的消息和不容易變化的邏輯分離竿屹。
3报强、機制和策略的分離。和第二點很像拱燃,本書中很多地方提到了機制和策略秉溉。上例中,我的理解碗誉,機制就是消息的處理邏輯召嘶,策略就是不同的消息處理(后面想專門寫一篇文章介紹下機制和策略)。
3哮缺、數(shù)據(jù)驅(qū)動編程可以用來做什么?
如上例所示弄跌,它可以應(yīng)用在函數(shù)級的設(shè)計中。
同時尝苇,它也可以應(yīng)用在程序級的設(shè)計中铛只,典型的比如用表驅(qū)動法實現(xiàn)一個狀態(tài)機(后面寫篇文章專門介紹)。
也可以用在系統(tǒng)級的設(shè)計中糠溜,比如DSL(這方面我經(jīng)驗有些欠缺淳玩,目前不是非常確定)。
它不是什么:
1非竿、 它不是一個全新的編程模型:它只是一種設(shè)計思路蜕着,而且歷史悠久,在unix/linux社區(qū)應(yīng)用很多红柱;
書中的值得思考的話:
數(shù)據(jù)壓倒一切众眨。如果選擇了正確的數(shù)據(jù)結(jié)構(gòu)并把一切組織的井井有條,正確的算法就不言自明容诬。編程的核心是數(shù)據(jù)結(jié)構(gòu)娩梨,而不是算法±劳剑——Rob Pike
程序員束手無策狈定。。习蓬。纽什。。只有跳脫代碼躲叼,直起腰芦缰,仔細思考數(shù)據(jù)才是最好的行動。表達式編程的精髓枫慷∪美伲——Fred Brooks
數(shù)據(jù)比程序邏輯更易駕馭。盡可能把設(shè)計的復(fù)雜度從代碼轉(zhuǎn)移至數(shù)據(jù)是個好實踐或听√叫ⅲ——《unix編程藝術(shù)》作者。
4誉裆、復(fù)雜一點的表驅(qū)動?
考慮一個消息(事件)驅(qū)動的系統(tǒng)顿颅,系統(tǒng)的某一模塊需要和其他的幾個模塊進行通信。它收到消息后找御,需要根據(jù)消息的發(fā)送方元镀,消息的類型,自身的狀態(tài)霎桅,進行不同的處理栖疑。比較常見的一個做法是用三個級聯(lián)的switch分支實現(xiàn)通過硬編碼來實現(xiàn):?
switch(sendMode)?
{?
? ? case:?
}?
switch(msgEvent)?
{?
? ? case:?
}?
switch(myStatus)?
{?
? ? case:?
}?
這種方法的缺點:
1、可讀性不高:找一個消息的處理部分代碼需要跳轉(zhuǎn)多層代碼滔驶。
2遇革、過多的switch分支,這其實也是一種重復(fù)代碼揭糕。他們都有共同的特性萝快,還可以再進一步進行提煉。
3著角、可擴展性差:如果為程序增加一種新的模塊的狀態(tài)揪漩,這可能要改變所有的消息處理的函數(shù),非常的不方便吏口,而且過程容易出錯蜀细。
4、程序缺少主心骨:缺少一個能夠提綱挈領(lǐng)的主干戈盈,程序的主干被淹沒在大量的代碼邏輯之中奠衔。
用表驅(qū)動法來實現(xiàn):
根據(jù)定義的三個枚舉:模塊類型,消息類型塘娶,自身模塊狀態(tài)归斤,定義一個函數(shù)跳轉(zhuǎn)表:
typedef struct? __EVENT_DRIVE?
{?
? ? MODE_TYPE mod;//消息的發(fā)送模塊?
? ? EVENT_TYPE event;//消息類型?
? ? STATUS_TYPE status;//自身狀態(tài)?
? ? EVENT_FUN eventfun;//此狀態(tài)下的處理函數(shù)指針?
}EVENT_DRIVE;?
EVENT_DRIVE eventdriver[] = //這就是一張表的定義,不一定是數(shù)據(jù)庫中的表血柳。也可以使自己定義的一個結(jié)構(gòu)體數(shù)組难捌。?
{?
? ? {MODE_A, EVENT_a, STATUS_1, fun1}?
? ? {MODE_A, EVENT_a, STATUS_2, fun2}?
? ? {MODE_A, EVENT_a, STATUS_3, fun3}?
? ? {MODE_A, EVENT_b, STATUS_1, fun4}?
? ? {MODE_A, EVENT_b, STATUS_2, fun5}?
? ? {MODE_B, EVENT_a, STATUS_1, fun6}?
? ? {MODE_B, EVENT_a, STATUS_2, fun7}?
? ? {MODE_B, EVENT_a, STATUS_3, fun8}?
? ? {MODE_B, EVENT_b, STATUS_1, fun9}?
? ? {MODE_B, EVENT_b, STATUS_2, fun10}?
};?
int driversize = sizeof(eventdriver) / sizeof(EVENT_DRIVE)//驅(qū)動表的大小?
EVENT_FUN GetFunFromDriver(MODE_TYPE mod, EVENT_TYPE event, STATUS_TYPE status)//驅(qū)動表查找函數(shù)?
{?
? ? int i = 0;?
? ? for (i = 0; i < driversize; i ++)?
? ? {?
? ? ? ? if ((eventdriver[i].mod == mod) && (eventdriver[i].event == event) && (eventdriver[i].status == status))?
? ? ? ? {?
? ? ? ? ? ? return eventdriver[i].eventfun;?
? ? ? ? }?
? ? }?
? ? return NULL;?
}?
這種方法的好處:
1、提高了程序的可讀性鸦难。一個消息如何處理根吁,只要看一下驅(qū)動表就知道,非常明顯拴事。
2沃斤、減少了重復(fù)代碼。這種方法的代碼量肯定比第一種少刃宵。為什么衡瓶?因為它把一些重復(fù)的東西:switch分支處理進行了抽象,把其中公共的東西——根據(jù)三個元素查找處理方法抽象成了一個函數(shù)GetFunFromDriver外加一個驅(qū)動表牲证。
3哮针、可擴展性。注意這個函數(shù)指針坦袍,他的定義其實就是一種契約十厢,類似于java中的接口,c++中的純虛函數(shù)捂齐,只有滿足這個條件(入?yún)ⅲ?a target="_blank" rel="nofollow">返回值)蛮放,才可以作為一個事件的處理函數(shù)。這個有一點插件結(jié)構(gòu)的味道奠宜,你可以對這些插件進行方便替換包颁,新增瞻想,刪除,從而改變程序的行為徘六。而這種改變内边,對事件處理函數(shù)的查找又是隔離的(也可以叫做隔離了變化)。待锈、
4漠其、程序有一個明顯的主干。
5竿音、降低了復(fù)雜度和屎。通過把程序邏輯的復(fù)雜度轉(zhuǎn)移到人類更容易處理的數(shù)據(jù)中來,從而達到控制復(fù)雜度的目標春瞬。
5柴信、繼承與組合
考慮一個事件驅(qū)動的模塊,這個模塊管理很多個用戶宽气,每個用戶需要處理很多的事件随常。那么,我們建立的驅(qū)動表就不是針對模塊了萄涯,而是針對用戶绪氛,應(yīng)該是用戶在某狀態(tài)下,收到某模塊的某事件的處理涝影。我們再假設(shè)用戶可以分為不同的級別枣察,每個級別對上面的提到的處理又不盡相同。
如果用表驅(qū)動法怎么實現(xiàn)片效?直接設(shè)計一個用戶的類红伦,沒有子類,也沒有具體的事件的處理方法淀衣。它有一個成員昙读,就是一個驅(qū)動表,它收到事件后膨桥,全部委托給這個驅(qū)動表去進行處理蛮浑。針對用戶的級別不同唠叛,可以定義多個不同的驅(qū)動表來裝配不同的對象實例。這個可以叫他組合法沮稚。
繼承和組合在《設(shè)計模式》也有提到艺沼。組合的優(yōu)勢在于它的可擴展性,彈性蕴掏,強調(diào)封裝性障般。(繼承和組合可以參考這篇文章:面向?qū)ο?/a>之繼承組合淺談)
至于這種情況下的驅(qū)動表,可以繼續(xù)使用結(jié)構(gòu)體盛杰,也可以使用對象挽荡。
上面的方法的一點性能優(yōu)化建議:
如果對性能要求不高,上面的方法足可以應(yīng)付即供。如果性能要求很高定拟,可以進行適當?shù)膬?yōu)化。比如逗嫡,可以建立一個多維數(shù)組青自,每一維分別表示模塊,狀態(tài)驱证,消息性穿。這樣,就可以根據(jù)這三者的枚舉直接根據(jù)下標定位到處理函數(shù)雷滚,而不是查表。(其實還是數(shù)據(jù)驅(qū)動的思想:數(shù)據(jù)結(jié)構(gòu)是靜態(tài)的算法吗坚。)
數(shù)據(jù)驅(qū)動編程再更高級祈远,更為抽象一點的,應(yīng)該就是流程腳本或者DSL了商源。我曾經(jīng)寫過一個簡單的寄生在xml上的腳本來描述流程车份。這一塊后面抽時間介紹。