深入數(shù)據(jù)驅(qū)動(dòng)編程之表驅(qū)動(dòng)法的詳解
更新時(shí)間:2013年05月23日 16:33:45 作者:
本篇文章是對表驅(qū)動(dòng)法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
數(shù)據(jù)驅(qū)動(dòng)編程之表驅(qū)動(dòng)法 本文示例代碼采用的是c語言。
之前介紹過數(shù)據(jù)驅(qū)動(dòng)編程《淺談:什么是數(shù)據(jù)驅(qū)動(dòng)編程的詳解》。里面介紹了一個(gè)簡單的數(shù)據(jù)驅(qū)動(dòng)手法。今天更進(jìn)一步,介紹一個(gè)稍微復(fù)雜,更加實(shí)用的一點(diǎn)手法——表驅(qū)動(dòng)法。
關(guān)于表驅(qū)動(dòng)法,在《unix編程藝術(shù)》中有提到,更詳細(xì)的描述可以看一下《代碼大全》,有一章專門進(jìn)行描述(大概是第八章)。
簡單的表驅(qū)動(dòng):
《淺談:什么是數(shù)據(jù)驅(qū)動(dòng)編程的詳解》中有一個(gè)代碼示例。它其實(shí)也可以看做是一種表驅(qū)動(dòng)手法,只不過這個(gè)表相對比較簡單,它在收到消息后,根據(jù)消息類型確定使用調(diào)用什么函數(shù)進(jìn)行處理。
復(fù)雜一點(diǎn)的表驅(qū)動(dòng):
考慮一個(gè)消息(事件)驅(qū)動(dòng)的系統(tǒng),系統(tǒng)的某一模塊需要和其他的幾個(gè)模塊進(jìn)行通信。它收到消息后,需要根據(jù)消息的發(fā)送方,消息的類型,自身的狀態(tài),進(jìn)行不同的處理。比較常見的一個(gè)做法是用三個(gè)級聯(lián)的switch分支實(shí)現(xiàn)通過硬編碼來實(shí)現(xiàn):
switch(sendMode)
{
case:
}
switch(msgEvent)
{
case:
}
switch(myStatus)
{
case:
}
這種方法的缺點(diǎn):
1、可讀性不高:找一個(gè)消息的處理部分代碼需要跳轉(zhuǎn)多層代碼。
2、過多的switch分支,這其實(shí)也是一種重復(fù)代碼。他們都有共同的特性,還可以再進(jìn)一步進(jìn)行提煉。
3、可擴(kuò)展性差:如果為程序增加一種新的模塊的狀態(tài),這可能要改變所有的消息處理的函數(shù),非常的不方便,而且過程容易出錯(cuò)。
4、程序缺少主心骨:缺少一個(gè)能夠提綱挈領(lǐng)的主干,程序的主干被淹沒在大量的代碼邏輯之中。
用表驅(qū)動(dòng)法來實(shí)現(xiàn):
根據(jù)定義的三個(gè)枚舉:模塊類型,消息類型,自身模塊狀態(tài),定義一個(gè)函數(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ù)庫中的表。也可以使自己定義的一個(gè)結(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ū)動(dòng)表的大小
EVENT_FUN GetFunFromDriver(MODE_TYPE mod, EVENT_TYPE event, STATUS_TYPE status)//驅(qū)動(dòng)表查找函數(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、提高了程序的可讀性。一個(gè)消息如何處理,只要看一下驅(qū)動(dòng)表就知道,非常明顯。
2、減少了重復(fù)代碼。這種方法的代碼量肯定比第一種少。為什么?因?yàn)樗岩恍┲貜?fù)的東西:switch分支處理進(jìn)行了抽象,把其中公共的東西——根據(jù)三個(gè)元素查找處理方法抽象成了一個(gè)函數(shù)GetFunFromDriver外加一個(gè)驅(qū)動(dòng)表。
3、可擴(kuò)展性。注意這個(gè)函數(shù)指針,他的定義其實(shí)就是一種契約,類似于java中的接口,c++中的純虛函數(shù),只有滿足這個(gè)條件(入?yún)?,返回值),才可以作為一個(gè)事件的處理函數(shù)。這個(gè)有一點(diǎn)插件結(jié)構(gòu)的味道,你可以對這些插件進(jìn)行方便替換,新增,刪除,從而改變程序的行為。而這種改變,對事件處理函數(shù)的查找又是隔離的(也可以叫做隔離了變化)。、
4、程序有一個(gè)明顯的主干。
5、降低了復(fù)雜度。通過把程序邏輯的復(fù)雜度轉(zhuǎn)移到人類更容易處理的數(shù)據(jù)中來,從而達(dá)到控制復(fù)雜度的目標(biāo)。
繼承與組合
考慮一個(gè)事件驅(qū)動(dòng)的模塊,這個(gè)模塊管理很多個(gè)用戶,每個(gè)用戶需要處理很多的事件。那么,我們建立的驅(qū)動(dòng)表就不是針對模塊了,而是針對用戶,應(yīng)該是用戶在某狀態(tài)下,收到某模塊的某事件的處理。我們再假設(shè)用戶可以分為不同的級別,每個(gè)級別對上面的提到的處理又不盡相同。
用面向?qū)ο蟮乃悸?,我們可以考慮設(shè)計(jì)一個(gè)用戶的基類,實(shí)現(xiàn)相同事件的處理方法;根據(jù)級別不同,定義幾個(gè)不同的子類,繼承公共的處理,再分別實(shí)現(xiàn)不同的處理。這是最常見的一種思路,可以叫它繼承法。
如果用表驅(qū)動(dòng)法怎么實(shí)現(xiàn)?直接設(shè)計(jì)一個(gè)用戶的類,沒有子類,也沒有具體的事件的處理方法。它有一個(gè)成員,就是一個(gè)驅(qū)動(dòng)表,它收到事件后,全部委托給這個(gè)驅(qū)動(dòng)表去進(jìn)行處理。針對用戶的級別不同,可以定義多個(gè)不同的驅(qū)動(dòng)表來裝配不同的對象實(shí)例。這個(gè)可以叫他組合法。
繼承和組合在《設(shè)計(jì)模式》也有提到。組合的優(yōu)勢在于它的可擴(kuò)展性,彈性,強(qiáng)調(diào)封裝性。
至于這種情況下的驅(qū)動(dòng)表,可以繼續(xù)使用結(jié)構(gòu)體,也可以使用對象。
上面的方法的一點(diǎn)性能優(yōu)化建議:
如果對性能要求不高,上面的方法足可以應(yīng)付。如果性能要求很高,可以進(jìn)行適當(dāng)?shù)膬?yōu)化。比如,可以建立一個(gè)多維數(shù)組,每一維分別表示模塊,狀態(tài),消息。這樣,就可以根據(jù)這三者的枚舉直接根據(jù)下標(biāo)定位到處理函數(shù),而不是查表。(其實(shí)還是數(shù)據(jù)驅(qū)動(dòng)的思想:數(shù)據(jù)結(jié)構(gòu)是靜態(tài)的算法。)
數(shù)據(jù)驅(qū)動(dòng)編程再更高級,更為抽象一點(diǎn)的,應(yīng)該就是流程腳本或者DSL了。我曾經(jīng)寫過一個(gè)簡單的寄生在xml上的腳本來描述流程。這一塊后面抽時(shí)間介紹。
之前介紹過數(shù)據(jù)驅(qū)動(dòng)編程《淺談:什么是數(shù)據(jù)驅(qū)動(dòng)編程的詳解》。里面介紹了一個(gè)簡單的數(shù)據(jù)驅(qū)動(dòng)手法。今天更進(jìn)一步,介紹一個(gè)稍微復(fù)雜,更加實(shí)用的一點(diǎn)手法——表驅(qū)動(dòng)法。
關(guān)于表驅(qū)動(dòng)法,在《unix編程藝術(shù)》中有提到,更詳細(xì)的描述可以看一下《代碼大全》,有一章專門進(jìn)行描述(大概是第八章)。
簡單的表驅(qū)動(dòng):
《淺談:什么是數(shù)據(jù)驅(qū)動(dòng)編程的詳解》中有一個(gè)代碼示例。它其實(shí)也可以看做是一種表驅(qū)動(dòng)手法,只不過這個(gè)表相對比較簡單,它在收到消息后,根據(jù)消息類型確定使用調(diào)用什么函數(shù)進(jìn)行處理。
復(fù)雜一點(diǎn)的表驅(qū)動(dòng):
考慮一個(gè)消息(事件)驅(qū)動(dòng)的系統(tǒng),系統(tǒng)的某一模塊需要和其他的幾個(gè)模塊進(jìn)行通信。它收到消息后,需要根據(jù)消息的發(fā)送方,消息的類型,自身的狀態(tài),進(jìn)行不同的處理。比較常見的一個(gè)做法是用三個(gè)級聯(lián)的switch分支實(shí)現(xiàn)通過硬編碼來實(shí)現(xiàn):
復(fù)制代碼 代碼如下:
switch(sendMode)
{
case:
}
switch(msgEvent)
{
case:
}
switch(myStatus)
{
case:
}
這種方法的缺點(diǎn):
1、可讀性不高:找一個(gè)消息的處理部分代碼需要跳轉(zhuǎn)多層代碼。
2、過多的switch分支,這其實(shí)也是一種重復(fù)代碼。他們都有共同的特性,還可以再進(jìn)一步進(jìn)行提煉。
3、可擴(kuò)展性差:如果為程序增加一種新的模塊的狀態(tài),這可能要改變所有的消息處理的函數(shù),非常的不方便,而且過程容易出錯(cuò)。
4、程序缺少主心骨:缺少一個(gè)能夠提綱挈領(lǐng)的主干,程序的主干被淹沒在大量的代碼邏輯之中。
用表驅(qū)動(dòng)法來實(shí)現(xiàn):
根據(jù)定義的三個(gè)枚舉:模塊類型,消息類型,自身模塊狀態(tài),定義一個(gè)函數(shù)跳轉(zhuǎn)表:
復(fù)制代碼 代碼如下:
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ù)庫中的表。也可以使自己定義的一個(gè)結(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ū)動(dòng)表的大小
EVENT_FUN GetFunFromDriver(MODE_TYPE mod, EVENT_TYPE event, STATUS_TYPE status)//驅(qū)動(dòng)表查找函數(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、提高了程序的可讀性。一個(gè)消息如何處理,只要看一下驅(qū)動(dòng)表就知道,非常明顯。
2、減少了重復(fù)代碼。這種方法的代碼量肯定比第一種少。為什么?因?yàn)樗岩恍┲貜?fù)的東西:switch分支處理進(jìn)行了抽象,把其中公共的東西——根據(jù)三個(gè)元素查找處理方法抽象成了一個(gè)函數(shù)GetFunFromDriver外加一個(gè)驅(qū)動(dòng)表。
3、可擴(kuò)展性。注意這個(gè)函數(shù)指針,他的定義其實(shí)就是一種契約,類似于java中的接口,c++中的純虛函數(shù),只有滿足這個(gè)條件(入?yún)?,返回值),才可以作為一個(gè)事件的處理函數(shù)。這個(gè)有一點(diǎn)插件結(jié)構(gòu)的味道,你可以對這些插件進(jìn)行方便替換,新增,刪除,從而改變程序的行為。而這種改變,對事件處理函數(shù)的查找又是隔離的(也可以叫做隔離了變化)。、
4、程序有一個(gè)明顯的主干。
5、降低了復(fù)雜度。通過把程序邏輯的復(fù)雜度轉(zhuǎn)移到人類更容易處理的數(shù)據(jù)中來,從而達(dá)到控制復(fù)雜度的目標(biāo)。
繼承與組合
考慮一個(gè)事件驅(qū)動(dòng)的模塊,這個(gè)模塊管理很多個(gè)用戶,每個(gè)用戶需要處理很多的事件。那么,我們建立的驅(qū)動(dòng)表就不是針對模塊了,而是針對用戶,應(yīng)該是用戶在某狀態(tài)下,收到某模塊的某事件的處理。我們再假設(shè)用戶可以分為不同的級別,每個(gè)級別對上面的提到的處理又不盡相同。
用面向?qū)ο蟮乃悸?,我們可以考慮設(shè)計(jì)一個(gè)用戶的基類,實(shí)現(xiàn)相同事件的處理方法;根據(jù)級別不同,定義幾個(gè)不同的子類,繼承公共的處理,再分別實(shí)現(xiàn)不同的處理。這是最常見的一種思路,可以叫它繼承法。
如果用表驅(qū)動(dòng)法怎么實(shí)現(xiàn)?直接設(shè)計(jì)一個(gè)用戶的類,沒有子類,也沒有具體的事件的處理方法。它有一個(gè)成員,就是一個(gè)驅(qū)動(dòng)表,它收到事件后,全部委托給這個(gè)驅(qū)動(dòng)表去進(jìn)行處理。針對用戶的級別不同,可以定義多個(gè)不同的驅(qū)動(dòng)表來裝配不同的對象實(shí)例。這個(gè)可以叫他組合法。
繼承和組合在《設(shè)計(jì)模式》也有提到。組合的優(yōu)勢在于它的可擴(kuò)展性,彈性,強(qiáng)調(diào)封裝性。
至于這種情況下的驅(qū)動(dòng)表,可以繼續(xù)使用結(jié)構(gòu)體,也可以使用對象。
上面的方法的一點(diǎn)性能優(yōu)化建議:
如果對性能要求不高,上面的方法足可以應(yīng)付。如果性能要求很高,可以進(jìn)行適當(dāng)?shù)膬?yōu)化。比如,可以建立一個(gè)多維數(shù)組,每一維分別表示模塊,狀態(tài),消息。這樣,就可以根據(jù)這三者的枚舉直接根據(jù)下標(biāo)定位到處理函數(shù),而不是查表。(其實(shí)還是數(shù)據(jù)驅(qū)動(dòng)的思想:數(shù)據(jù)結(jié)構(gòu)是靜態(tài)的算法。)
數(shù)據(jù)驅(qū)動(dòng)編程再更高級,更為抽象一點(diǎn)的,應(yīng)該就是流程腳本或者DSL了。我曾經(jīng)寫過一個(gè)簡單的寄生在xml上的腳本來描述流程。這一塊后面抽時(shí)間介紹。
相關(guān)文章
Linux內(nèi)存描述符mm_struct實(shí)例詳解
Linux對于內(nèi)存的管理涉及到非常多的方面,這篇文章首先從對進(jìn)程虛擬地址空間的管理說起,具體實(shí)例代碼大家通過本文學(xué)習(xí)下吧2017-09-09
Linux目錄結(jié)構(gòu)以及目錄內(nèi)的主要內(nèi)容詳細(xì)解析
以下是對Linux下的目錄結(jié)構(gòu)以及目錄內(nèi)的主要內(nèi)容進(jìn)行了詳細(xì)的解析介紹。需要的朋友可以過來參考下2013-08-08
linux系統(tǒng)下ubuntu重啟apache服務(wù)命令
在Linux中要重啟apache服務(wù)與在windows是有很大的區(qū)別,下面我們來介紹一下常用的命令,需要的朋友參考下吧2017-06-06
Linux 使用rpm方式安裝最新mysql(5.7.16)步驟及常見問題解決方法
前幾天在阿里云買了個(gè)服務(wù)器 ,準(zhǔn)備自己玩玩,現(xiàn)將最新版mysql(5.7.16)安裝步驟,以及遇到問題及解決過程分享,需要的朋友參考下吧2017-01-01

