天天看點

資料驅動程式設計之表驅動法

本文示例代碼采用的是c語言。

關于表驅動法,在《unix程式設計藝術》中有提到,更詳細的描述可以看一下《代碼大全》,有一章專門進行描述(大概是第八章)。

簡單的表驅動:

複雜一點的表驅動:

考慮一個消息(事件)驅動的系統,系統的某一子產品需要和其他的幾個子產品進行通信。它收到消息後,需要根據消息的發送方,消息的類型,自身的狀态,進行不同的處理。比較常見的一個做法是用三個級聯的switch分支實作通過寫死來實作:

<a href="http://blog.csdn.net/chgaowei/article/details/6966857">view plain</a>

switch(sendMode)  

{  

    case:  

}  

switch(msgEvent)  

switch(myStatus)  

這種方法的缺點:

1、可讀性不高:找一個消息的處理部分代碼需要跳轉多層代碼。

2、過多的switch分支,這其實也是一種重複代碼。他們都有共同的特性,還可以再進一步進行提煉。

3、可擴充性差:如果為程式增加一種新的子產品的狀态,這可能要改變所有的消息處理的函數,非常的不友善,而且過程容易出錯。

4、程式缺少主心骨:缺少一個能夠提綱挈領的主幹,程式的主幹被淹沒在大量的代碼邏輯之中。

用表驅動法來實作:

根據定義的三個枚舉:子產品類型,消息類型,自身子產品狀态,定義一個函數跳轉表:

typedef struct  __EVENT_DRIVE  

    MODE_TYPE mod;//消息的發送子產品  

    EVENT_TYPE event;//消息類型  

    STATUS_TYPE status;//自身狀态  

    EVENT_FUN eventfun;//此狀态下的處理函數指針  

}EVENT_DRIVE;  

EVENT_DRIVE eventdriver[] = //這就是一張表的定義,不一定是資料庫中的表。也可以使自己定義的一個結構體數組。  

    {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)//驅動表的大小  

EVENT_FUN GetFunFromDriver(MODE_TYPE mod, EVENT_TYPE event, STATUS_TYPE status)//驅動表查找函數  

    int i = 0;  

    for (i = 0; i &lt; driversize; i ++)  

    {  

        if ((eventdriver[i].mod == mod) &amp;&amp; (eventdriver[i].event == event) &amp;&amp; (eventdriver[i].status == status))  

        {  

            return eventdriver[i].eventfun;  

        }  

    }  

    return NULL;  

這種方法的好處:

1、提高了程式的可讀性。一個消息如何處理,隻要看一下驅動表就知道,非常明顯。

2、減少了重複代碼。這種方法的代碼量肯定比第一種少。為什麼?因為它把一些重複的東西:switch分支處理進行了抽象,把其中公共的東西——根據三個元素查找處理方法抽象成了一個函數GetFunFromDriver外加一個驅動表。

3、可擴充性。注意這個函數指針,他的定義其實就是一種契約,類似于java中的接口,c++中的純虛函數,隻有滿足這個條件(入參,傳回值),才可以作為一個事件的處理函數。這個有一點插件結構的味道,你可以對這些插件進行友善替換,新增,删除,進而改變程式的行為。而這種改變,對事件處理函數的查找又是隔離的(也可以叫做隔離了變化)。、

4、程式有一個明顯的主幹。

5、降低了複雜度。通過把程式邏輯的複雜度轉移到人類更容易處理的資料中來,進而達到控制複雜度的目标。

繼承與組合

考慮一個事件驅動的子產品,這個子產品管理很多個使用者,每個使用者需要處理很多的事件。那麼,我們建立的驅動表就不是針對子產品了,而是針對使用者,應該是使用者在某狀态下,收到某子產品的某事件的處理。我們再假設使用者可以分為不同的級别,每個級别對上面的提到的處理又不盡相同。

用面向對象的思路,我們可以考慮設計一個使用者的基類,實作相同僚件的處理方法;根據級别不同,定義幾個不同的子類,繼承公共的處理,再分别實作不同的處理。這是最常見的一種思路,可以叫它繼承法。

如果用表驅動法怎麼實作?直接設計一個使用者的類,沒有子類,也沒有具體的事件的處理方法。它有一個成員,就是一個驅動表,它收到事件後,全部委托給這個驅動表去進行處理。針對使用者的級别不同,可以定義多個不同的驅動表來裝配不同的對象執行個體。這個可以叫他組合法。

至于這種情況下的驅動表,可以繼續使用結構體,也可以使用對象。

上面的方法的一點性能優化建議:

如果對性能要求不高,上面的方法足可以應付。如果性能要求很高,可以進行适當的優化。比如,可以建立一個多元數組,每一維分别表示子產品,狀态,消息。這樣,就可以根據這三者的枚舉直接根據下标定位到處理函數,而不是查表。(其實還是資料驅動的思想:資料結構是靜态的算法。)

資料驅動程式設計再更進階,更為抽象一點的,應該就是流程腳本或者DSL了。我曾經寫過一個簡單的寄生在xml上的腳本來描述流程。這一塊後面抽時間介紹。

繼續閱讀