深入數(shù)據(jù)驅(qū)動編程之表驅(qū)動法的詳解
更新時間:2013年05月23日 16:33:45 作者:
本篇文章是對表驅(qū)動法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
數(shù)據(jù)驅(qū)動編程之表驅(qū)動法 本文示例代碼采用的是c語言。
之前介紹過數(shù)據(jù)驅(qū)動編程《淺談:什么是數(shù)據(jù)驅(qū)動編程的詳解》。里面介紹了一個簡單的數(shù)據(jù)驅(qū)動手法。今天更進(jìn)一步,介紹一個稍微復(fù)雜,更加實用的一點手法——表驅(qū)動法。
關(guān)于表驅(qū)動法,在《unix編程藝術(shù)》中有提到,更詳細(xì)的描述可以看一下《代碼大全》,有一章專門進(jìn)行描述(大概是第八章)。
簡單的表驅(qū)動:
《淺談:什么是數(shù)據(jù)驅(qū)動編程的詳解》中有一個代碼示例。它其實也可以看做是一種表驅(qū)動手法,只不過這個表相對比較簡單,它在收到消息后,根據(jù)消息類型確定使用調(diào)用什么函數(shù)進(jìn)行處理。
復(fù)雜一點的表驅(qū)動:
考慮一個消息(事件)驅(qū)動的系統(tǒng),系統(tǒng)的某一模塊需要和其他的幾個模塊進(jìn)行通信。它收到消息后,需要根據(jù)消息的發(fā)送方,消息的類型,自身的狀態(tài),進(jìn)行不同的處理。比較常見的一個做法是用三個級聯(lián)的switch分支實現(xiàn)通過硬編碼來實現(xiàn):
switch(sendMode)
{
case:
}
switch(msgEvent)
{
case:
}
switch(myStatus)
{
case:
}
這種方法的缺點:
1、可讀性不高:找一個消息的處理部分代碼需要跳轉(zhuǎn)多層代碼。
2、過多的switch分支,這其實也是一種重復(fù)代碼。他們都有共同的特性,還可以再進(jìn)一步進(jìn)行提煉。
3、可擴(kuò)展性差:如果為程序增加一種新的模塊的狀態(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ìn)行了抽象,把其中公共的東西——根據(jù)三個元素查找處理方法抽象成了一個函數(shù)GetFunFromDriver外加一個驅(qū)動表。
3、可擴(kuò)展性。注意這個函數(shù)指針,他的定義其實就是一種契約,類似于java中的接口,c++中的純虛函數(shù),只有滿足這個條件(入?yún)ⅲ祷刂担?,才可以作為一個事件的處理函數(shù)。這個有一點插件結(jié)構(gòu)的味道,你可以對這些插件進(jìn)行方便替換,新增,刪除,從而改變程序的行為。而這種改變,對事件處理函數(shù)的查找又是隔離的(也可以叫做隔離了變化)。、
4、程序有一個明顯的主干。
5、降低了復(fù)雜度。通過把程序邏輯的復(fù)雜度轉(zhuǎn)移到人類更容易處理的數(shù)據(jù)中來,從而達(dá)到控制復(fù)雜度的目標(biāo)。
繼承與組合
考慮一個事件驅(qū)動的模塊,這個模塊管理很多個用戶,每個用戶需要處理很多的事件。那么,我們建立的驅(qū)動表就不是針對模塊了,而是針對用戶,應(yīng)該是用戶在某狀態(tài)下,收到某模塊的某事件的處理。我們再假設(shè)用戶可以分為不同的級別,每個級別對上面的提到的處理又不盡相同。
用面向?qū)ο蟮乃悸罚覀兛梢钥紤]設(shè)計一個用戶的基類,實現(xiàn)相同事件的處理方法;根據(jù)級別不同,定義幾個不同的子類,繼承公共的處理,再分別實現(xiàn)不同的處理。這是最常見的一種思路,可以叫它繼承法。
如果用表驅(qū)動法怎么實現(xiàn)?直接設(shè)計一個用戶的類,沒有子類,也沒有具體的事件的處理方法。它有一個成員,就是一個驅(qū)動表,它收到事件后,全部委托給這個驅(qū)動表去進(jìn)行處理。針對用戶的級別不同,可以定義多個不同的驅(qū)動表來裝配不同的對象實例。這個可以叫他組合法。
繼承和組合在《設(shè)計模式》也有提到。組合的優(yōu)勢在于它的可擴(kuò)展性,彈性,強(qiáng)調(diào)封裝性。
至于這種情況下的驅(qū)動表,可以繼續(xù)使用結(jié)構(gòu)體,也可以使用對象。
上面的方法的一點性能優(yōu)化建議:
如果對性能要求不高,上面的方法足可以應(yīng)付。如果性能要求很高,可以進(jìn)行適當(dāng)?shù)膬?yōu)化。比如,可以建立一個多維數(shù)組,每一維分別表示模塊,狀態(tài),消息。這樣,就可以根據(jù)這三者的枚舉直接根據(jù)下標(biāo)定位到處理函數(shù),而不是查表。(其實還是數(shù)據(jù)驅(qū)動的思想:數(shù)據(jù)結(jié)構(gòu)是靜態(tài)的算法。)
數(shù)據(jù)驅(qū)動編程再更高級,更為抽象一點的,應(yīng)該就是流程腳本或者DSL了。我曾經(jīng)寫過一個簡單的寄生在xml上的腳本來描述流程。這一塊后面抽時間介紹。
之前介紹過數(shù)據(jù)驅(qū)動編程《淺談:什么是數(shù)據(jù)驅(qū)動編程的詳解》。里面介紹了一個簡單的數(shù)據(jù)驅(qū)動手法。今天更進(jìn)一步,介紹一個稍微復(fù)雜,更加實用的一點手法——表驅(qū)動法。
關(guān)于表驅(qū)動法,在《unix編程藝術(shù)》中有提到,更詳細(xì)的描述可以看一下《代碼大全》,有一章專門進(jìn)行描述(大概是第八章)。
簡單的表驅(qū)動:
《淺談:什么是數(shù)據(jù)驅(qū)動編程的詳解》中有一個代碼示例。它其實也可以看做是一種表驅(qū)動手法,只不過這個表相對比較簡單,它在收到消息后,根據(jù)消息類型確定使用調(diào)用什么函數(shù)進(jìn)行處理。
復(fù)雜一點的表驅(qū)動:
考慮一個消息(事件)驅(qū)動的系統(tǒng),系統(tǒng)的某一模塊需要和其他的幾個模塊進(jìn)行通信。它收到消息后,需要根據(jù)消息的發(fā)送方,消息的類型,自身的狀態(tài),進(jìn)行不同的處理。比較常見的一個做法是用三個級聯(lián)的switch分支實現(xiàn)通過硬編碼來實現(xiàn):
復(fù)制代碼 代碼如下:
switch(sendMode)
{
case:
}
switch(msgEvent)
{
case:
}
switch(myStatus)
{
case:
}
這種方法的缺點:
1、可讀性不高:找一個消息的處理部分代碼需要跳轉(zhuǎn)多層代碼。
2、過多的switch分支,這其實也是一種重復(fù)代碼。他們都有共同的特性,還可以再進(jìn)一步進(jìn)行提煉。
3、可擴(kuò)展性差:如果為程序增加一種新的模塊的狀態(tài),這可能要改變所有的消息處理的函數(shù),非常的不方便,而且過程容易出錯。
4、程序缺少主心骨:缺少一個能夠提綱挈領(lǐng)的主干,程序的主干被淹沒在大量的代碼邏輯之中。
用表驅(qū)動法來實現(xiàn):
根據(jù)定義的三個枚舉:模塊類型,消息類型,自身模塊狀態(tài),定義一個函數(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ù)庫中的表。也可以使自己定義的一個結(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ìn)行了抽象,把其中公共的東西——根據(jù)三個元素查找處理方法抽象成了一個函數(shù)GetFunFromDriver外加一個驅(qū)動表。
3、可擴(kuò)展性。注意這個函數(shù)指針,他的定義其實就是一種契約,類似于java中的接口,c++中的純虛函數(shù),只有滿足這個條件(入?yún)ⅲ祷刂担?,才可以作為一個事件的處理函數(shù)。這個有一點插件結(jié)構(gòu)的味道,你可以對這些插件進(jìn)行方便替換,新增,刪除,從而改變程序的行為。而這種改變,對事件處理函數(shù)的查找又是隔離的(也可以叫做隔離了變化)。、
4、程序有一個明顯的主干。
5、降低了復(fù)雜度。通過把程序邏輯的復(fù)雜度轉(zhuǎn)移到人類更容易處理的數(shù)據(jù)中來,從而達(dá)到控制復(fù)雜度的目標(biāo)。
繼承與組合
考慮一個事件驅(qū)動的模塊,這個模塊管理很多個用戶,每個用戶需要處理很多的事件。那么,我們建立的驅(qū)動表就不是針對模塊了,而是針對用戶,應(yīng)該是用戶在某狀態(tài)下,收到某模塊的某事件的處理。我們再假設(shè)用戶可以分為不同的級別,每個級別對上面的提到的處理又不盡相同。
用面向?qū)ο蟮乃悸罚覀兛梢钥紤]設(shè)計一個用戶的基類,實現(xiàn)相同事件的處理方法;根據(jù)級別不同,定義幾個不同的子類,繼承公共的處理,再分別實現(xiàn)不同的處理。這是最常見的一種思路,可以叫它繼承法。
如果用表驅(qū)動法怎么實現(xiàn)?直接設(shè)計一個用戶的類,沒有子類,也沒有具體的事件的處理方法。它有一個成員,就是一個驅(qū)動表,它收到事件后,全部委托給這個驅(qū)動表去進(jìn)行處理。針對用戶的級別不同,可以定義多個不同的驅(qū)動表來裝配不同的對象實例。這個可以叫他組合法。
繼承和組合在《設(shè)計模式》也有提到。組合的優(yōu)勢在于它的可擴(kuò)展性,彈性,強(qiáng)調(diào)封裝性。
至于這種情況下的驅(qū)動表,可以繼續(xù)使用結(jié)構(gòu)體,也可以使用對象。
上面的方法的一點性能優(yōu)化建議:
如果對性能要求不高,上面的方法足可以應(yīng)付。如果性能要求很高,可以進(jìn)行適當(dāng)?shù)膬?yōu)化。比如,可以建立一個多維數(shù)組,每一維分別表示模塊,狀態(tài),消息。這樣,就可以根據(jù)這三者的枚舉直接根據(jù)下標(biāo)定位到處理函數(shù),而不是查表。(其實還是數(shù)據(jù)驅(qū)動的思想:數(shù)據(jù)結(jié)構(gòu)是靜態(tài)的算法。)
數(shù)據(jù)驅(qū)動編程再更高級,更為抽象一點的,應(yīng)該就是流程腳本或者DSL了。我曾經(jīng)寫過一個簡單的寄生在xml上的腳本來描述流程。這一塊后面抽時間介紹。
相關(guān)文章
Linux C中庫函數(shù)與系統(tǒng)調(diào)用的區(qū)別詳細(xì)解析
以下是對Linux下C中庫函數(shù)和系統(tǒng)調(diào)用的區(qū)別進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過來參考下2013-08-08ubuntu14.04中安裝chkconfig服務(wù)管理程序的方法
這篇文章主要介紹了ubuntu14.04中安裝chkconfig服務(wù)管理程序的方法,在文章給大家提到了ubuntu使用chkconfig命令來管理服務(wù) 的命令寫法,需要的朋友可以參考下2018-03-03Linux rdesktop操作系統(tǒng)下遠(yuǎn)程登錄Windows XP桌面
眾所周知XP下有"遠(yuǎn)程桌面連接"用來遠(yuǎn)程登錄桌面,設(shè)置也非常簡單。那有沒有什么辦法在linux下遠(yuǎn)程登錄到XP呢?有。用rdesktop這個linux下的軟件就能實現(xiàn)。2008-04-04Linux crontab 命令格式與詳細(xì)示例(推薦)
這篇文章主要介紹了Linux crontab 命令格式與詳細(xì)示例(推薦),需要的朋友可以參考下2017-07-07Linux/Unix環(huán)境下的Make和Makefile詳解
2008-02-02