用表驅動代替switch-case

不知道從什麼時候開始,switch-case語句成了代碼壞味道的代名詞,寫代碼的時候小心翼翼地避開它,看到別人代碼中的switch-case就皺眉頭,想想其實大可不必這樣,switch-case語句並不是代碼壞味道的根源,壞味道來自糟糕的代碼(結構)設計,比如過多的switch-case分支,或者多重switch-case嵌套等等,這些都將導致代碼可讀性下降,如果再加上代碼風格較差,代碼不對齊,那麼壞味道就相當地大了。

    簡短的switch-case還是繼續用吧,但是對於分支太多的長switch-case最好能想辦法化解開,那麼什麼算長什麼算短呢?我也不知道,就以在最低分辨率的顯示器上能夠在一個窗口中完整顯示整個switch-case塊爲判斷依據吧。化解長switch-case的方法有很多種,用函數封裝或者宏取代case塊是治標不治本的方法,使用表驅動通常是治療這種頑症的有效方法,本文將介紹如何用表驅動方法化解長switch-case。

    還是用例子說明問題吧,假設我們要爲一個系統編寫驅動,系統已經定義好了如下所示的複用接口(MUX):

STATUS DriverIoControl(UINT function_no, PVOID para_in, PVOID para_out)

用戶層程序通過複用接口調用驅動,功能號就是function_no,驅動程序負責實現具體的DriverIoControl()函數完成相應的功能。這是使用switch-case的典型場景,先看一個使用switch-case的方案:

STATUS DriverIoControl(UINT function_no, PVOID para_in, PVOID para_out)
{
    STATUS rc;

    switch(function_no)
    {
        case PROCESSA:
            rc = ProcessA(para_in,para_out);
            break;
        case PROCESSB:
            rc = ProcessB(para_in,para_out);
            break;
        case PROCESSC:
            rc = ProcessC(para_in,para_out);
            break;
            
        ..........

        
        default:
            rc = UN_SUPPORT;
            break
    }

    return rc;
}

                                                                        
STATUS ProcessA(PVOID para_in, PVOID para_out)
{
    //一些代碼....
}

STATUS ProcessB(PVOID para_in, PVOID para_out)
{
    //一些代碼....
}

STATUS ProcessC(PVOID para_in, PVOID para_out)
{
    //一些代碼....
}

................

這個方案中規中矩,但是如果驅動很複雜,功能很多,那麼DriverIoControl函數代碼的長度是相當可觀的,好像已經聞到壞味道了,呵呵。現在換成使用宏的解決方案:

#define DISPATCH_BEGIN(func)      switch(func) /
                                  {

#define DISPATCH_FUNCTION(func_c, function) case func_c: /
                                                rc = function(para_in,para_out); /
                                              break;

#define DISPATCH_END(code)        default: /
                                      rc = code; /
                                   }


STATUS DriverIoControl(UINT function_no, PVOID para_in, PVOID para_out)
{
    STATUS rc;

    DISPATCH_BEGIN(function_no)
    DISPATCH_FUNCTION(PROCESSA,ProcessA)
    DISPATCH_FUNCTION(PROCESSB,ProcessB)
    DISPATCH_FUNCTION(PROCESSC,ProcessC)

    ........................
    
    DISPATCH_END(UN_SUPPORT)

    return rc;
}

嗯,好一點,但好不到哪裏去,只是用一行代替多行而已,並不能改變代碼隨着功能增多線性增長的趨勢。羅嗦一下,我不喜歡宏的原因很簡單,目前很少有(說實話,是我確實沒有見過)調試器支持對宏的展開調試。這很麻煩,當一段摻雜着宏的代碼沒有達到預期的目的時,你不得不一遍一遍地在心裏展開你的宏,以確定它是沒有問題的(或者,你根本不能確定,只能假設它沒有問題)。

    現在看看錶驅動方案,如果系統約定的function_no是個連續的線性數字,或者是某個區間上的連續線性數字,那麼驅動表可以簡單就是一個線性數組,看看例子:

typedef STATUS (*ProcessFuncPtr)(PVOID para_in, PVOID para_out);

typedef struct tagDispatchItem
{
    ProcessFuncPtr func_ptr;
}DISPATCH_ITEM;


DISPATCH_ITEM  dispatch_table[MAX_DISPATCH_ITEM];

STATUS DriverIoControl(UINT function_no, PVOID para_in, PVOID para_out)
{
    /*或者需要對function_no調整,使其區間落到[0 - MAX_DISPATCH_ITEM)*/
    if((function_no >= 0) && (function_no < MAX_DISPATCH_ITEM))
        return dispatch_table[function_no].func_ptr(para_in,para_out);
    else
        return UN_SUPPORT;
}


如果function_no不是線性的,那就需要查表:

typedef struct tagDispatchItem
{
    UNIT func_no;
    ProcessFuncPtr func_ptr;
}DISPATCH_ITEM;

DISPATCH_ITEM  dispatch_table[MAX_DISPATCH_ITEM];

STATUS DriverIoControl(UINT function_no, PVOID para_in, PVOID para_out)
{
    int i,find = -1;
    
    for(i = 0; i < MAX_DISPATCH_ITEM; i++)
    {
        if(function_no == dispatch_table[i].func_no)
        {
            return dispatch_table[i].func_ptr(para_in,para_out);
        }
    }

    return UN_SUPPORT;
}


使用表驅動的好處就是DriverIoControl的代碼就這幾行,添加新功能,只需要維護驅動表dispatch_table就行了,就這樣擺脫了冗長乏味的switch-case。

    前面例子中的switch-case語句中各個case分支參數比較簡單整齊,也就是各個case分支都是用相同的參數para_in和para_out,如果各個分支使用的參數不整齊怎麼辦?那就需要封裝,通常是用struct和union結合定義一個統一的數據結構做爲接口參數,不同的分支dispatch函數內部根據需要從這個統一的數據結構中提取相應的數據。具體方法就是首先定義統一的dispatch函數接口,例如:

typedef STATUS (*ProcessFuncPtr)(DISPATCH_DATA *para);

然後用struct和union相結合定義DISPATCH_DATA,DISPATCH_DATA通常有這樣的結構:

typedef struct tagDISPATCH_DATA
{
    //此處聲明所有的分支公用的數據
    union
    {
        //此處聲明processA分支使用的數據
    }processA;
    union
    {
        //此處聲明processA分支使用的數據
    }processB;
    union
    {
        //此處聲明processA分支使用的數據
    }processC;
    ......
}DISPATCH_DATA;

做過Windows驅動程序的朋友肯定對IRP不陌生,IRP就是一個典型的例子。

    就這樣吧,打完,收工。


發佈了0 篇原創文章 · 獲贊 4 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章