2018-08-17 數(shù)據(jù)驅(qū)動編程與表驅(qū)動法

轉(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)用很多红柱;

2承匣、它不同于面向?qū)ο?/a>設(shè)計中的數(shù)據(jù):“數(shù)據(jù)驅(qū)動編程中,數(shù)據(jù)不但表示了某個對象的狀態(tài)豹芯,實際上還定義了程序的流程悄雅;OO看重的是封裝,而數(shù)據(jù)驅(qū)動編程看重的是編寫盡可能少的代碼铁蹈】硐校”

書中的值得思考的話:

數(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ū)ο?/a>的思路燃逻,我們可以考慮設(shè)計一個用戶的基類序目,實現(xiàn)相同事件的處理方法;根據(jù)級別不同伯襟,定義幾個不同的子類猿涨,繼承公共的處理,再分別實現(xiàn)不同的處理逗旁。這是最常見的一種思路嘿辟,可以叫它繼承法。

如果用表驅(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上的腳本來描述流程车份。這一塊后面抽時間介紹。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末牡彻,一起剝皮案震驚了整個濱河市扫沼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌庄吼,老刑警劉巖缎除,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異总寻,居然都是意外死亡器罐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門渐行,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轰坊,“玉大人铸董,你說我怎么就攤上這事‰饶” “怎么了粟害?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長颤芬。 經(jīng)常有香客問我悲幅,道長,這世上最難降的妖魔是什么驻襟? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任夺艰,我火速辦了婚禮,結(jié)果婚禮上沉衣,老公的妹妹穿的比我還像新娘郁副。我一直安慰自己,他們只是感情好豌习,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布存谎。 她就那樣靜靜地躺著,像睡著了一般肥隆。 火紅的嫁衣襯著肌膚如雪既荚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天栋艳,我揣著相機與錄音恰聘,去河邊找鬼。 笑死吸占,一個胖子當著我的面吹牛晴叨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播矾屯,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼兼蕊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了件蚕?” 一聲冷哼從身側(cè)響起孙技,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎排作,沒想到半個月后牵啦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡纽绍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年蕾久,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡僧著,死狀恐怖樟遣,靈堂內(nèi)的尸體忽然破棺而出煌妈,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布汇歹,位于F島的核電站屯阀,受9級特大地震影響救欧,放射性物質(zhì)發(fā)生泄漏值戳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一愈腾、第九天 我趴在偏房一處隱蔽的房頂上張望憋活。 院中可真熱鬧,春花似錦虱黄、人聲如沸悦即。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辜梳。三九已至,卻和暖如春泳叠,著一層夾襖步出監(jiān)牢的瞬間作瞄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工危纫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留宗挥,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓种蝶,卻偏偏與公主長得像属韧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蛤吓,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容