表格驱动编程

1. 毕业设计中的使用

第一次使用表格驱动编程,是在大学毕业设计的时候。做一个LL(1)的词法分析程序,需要读取终结符、非终结符、以及推导公式。程序会根据以上信息生成FIRST集合和LAST集合,然后根据递归下降分析的方法,就可以判断当前的表达式,是否符合对应的文法规则了。因为在代码中对于推导用到的表格没有写为Hard Code(写死,硬编码),所以可以适应很多的符合规则的文法。

2. 开源代码中的使用

第二次遇到表格驱动编程的方法,是在LXGJ,做jpg图像的压缩,用的是jpeg2000。当时还写了一篇文章,总结了一下,具体见 http://www.cnblogs.com/Dennis-mi/p/3871860.html 。这里的代码采用的是动态生成表格的方式,在函数中通过宏来控制表格的大小,来实现动态的支持不同图片格式的解析。

3. 工作中使用

在LXGJ的时候,领导布置了一个任务,需要用户选择从不同的来源生成PDF,大致有扫描、文件、图片……,需要做成一个类似于Eclipse(Visual Studio)生成工程的向导类似的。然后明白了需求以后,我就开始写代码了。然后项目经理过来问我做的怎么样了,我把写了好多 if else的代码拿给他看,他说“你怎么能这么写呢,万一需求有变化,怎么改。 代码类似于

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
if( SCAN == type )
{
    dlg = new ScanDialog();
    dlg.show();
    if(DPI300 == dlg.getDpi())
    {
        .....
    }
}
else if(FILE == type)
{ 
    ...
}
else if(IMAGE == type)
{
    ...
}

以上的代码在遇到规则有变化,或者每一步方式的选项要增加或者减少的时候,修改比较麻烦。 项目经理的建议

1
2
3
4
5
6
7
Dialog choiceDialog[4][5]=
{
    new ScanDialog(),new DpiDialog(),new xxDialog(),NULL,
    new FileDialog(),new xxDialog(),NULL,NULL,
    new ImageDialog(),new DpiDialog(),new ImageTypeDialog(),new xxDialog(),NULL,
    NULL,NULL,NULL,NULL,NULL,
}

采用下面的这种写法具有更好的可扩展性和灵活性。如果需要增加一种方式,则可以增加一行,如果需要增加方式中的一个选项,则可以在某行的某列后面增加一个dialog,用NULL表示结束,可以每种方式经过的选项不同。

4.我的新发现

在LXSJ的时候,我们需要对APP登录做验证,验证的过程比较多而且规则有可能会变化。这个时候采用表格驱动编程是一种很好的方式。举例说明如下: 比如我们需要验证

1、用户是否余额—>有余额允许登录,没有不允许;
2、用户是否是VIP会员—>是会员允许登录,不管其他条件;
3、用户如果是合作方的用户—>则合作方决定是否允许登录;
4、用户如果已经是老用户—>是允许登录,不是不允许登录;

此时可以采用老办法,用if else来写。但是新的方法是采用表格驱动,具体如下。 表格的每一项对应一个判断函数,返回 bool 值。

1
2
3
4
5
6
7
loginLogicTable[][] =
{
    /*当前需要进行的判断,成功时的状态,失败时的状态*/
    LOGIN_START,IS_VIP,IS_VIP,
    IS_VIP,LOGIN_SUCCEED,HAS_BALANCE,
    HAS_BALANCE,LOGIN_SUCCEED,IS_REGULAR_CUSTOMER,   IS_REGULAR_CUSTOMER,LOGIN_SUCEED,IS_PARTER_CUSTOMER,   IS_PARTER_CUSTOMER,SEND_REQUEST_TO_PARTER,LOGIN_FAILED,   RECIVE_PARTER_RESPONSE,LOGIN_SUCCEED,LOGIN_FAILED,
}

以上是此时规则的描述,其中 LOGIN_SUCCEEDLOGIN_FAILED 是最终的状态。 第一项对应着一个函数,使用这个表格的伪代码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
bool isLoginSucceed(userInfo)
{
    curState = LOGIN_START;
    while( curState != LOGIN_SUCCEED && curState != LOGIN_FAILED)
    { 
        changeArray = findArray(curState); //返回loginLoginTable中的一行
        if(callFunc(changeArray[0])
        { 
            curState=changeArray[1];        
        }
        else
        {
            curState = changeArray[2];
        } 
    }    
    return curState == LOGIN_SUCCEED;
}

如果不需要判断是否是合作伙伴的用户了。则将

1
2
IS_REGULAR_CUSTOMER,LOGIN_SUCEED,[IS_PARTER_CUSTOMER], //改为
IS_REGULAR_CUSTOMER,LOGIN_SUCEED,[LOGIN_FAILED],

就可以了,改动非常的方便。 如果要增加一个判断,比如要增加xyz指令判断。 则修改如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
IS_REGULAR_CUSTOMER,[LOGIN_SUCEED],IS_PARTER_CUSTOMER,  
IS_PARTER_CUSTOMER,SEND_REQUEST_TO_PARTER,LOGIN_FAILED,
RECIVE_PARTER_RESPONSE,LOGIN_SUCCEED,LOGIN_FAILED,

/*改为*/

IS_REGULAR_CUSTOMER,[JUDGE_XYZ_CMD],IS_PARTER_CUSTOMER,
IS_PARTER_CUSTOMER,SEND_REQUEST_TO_PARTER,LOGIN_FAILED,
RECIVE_PARTER_RESPONSE,[JUDGE_XYZ_CMD],LOGIN_FAILED,
[JUDGE_XYZ_CMD],LOGIN_SUCCEED,LOGIN_FAILED,