每个码农对于if-else都很熟悉,他代表了程序最基本最重要的结构-逻辑控制,可以说,只要我们还在写代码,这种控制语句就无法避免。但是在某些情况下,我们可以想办法优化他。

首先看一段代码

// Javascript
getweek(value){
    this.dayOfWeek = value;
    if(this.dayOfWeek == 1){
        this.Week = "星期一";
    }else if(this.dayOfWeek == 2){
        this.Week = "星期二";
    }else if(this.dayOfWeek == 3){
        this.Week = "星期三";
    }else if(this.dayOfWeek == 4){
        this.Week = "星期四";
    }else if(this.dayOfWeek == 5){
        this.Week = "星期五";
    }else if(this.dayOfWeek == 6){
        this.Week = "星期六";
    }else if(this.dayOfWeek == 0){
        this.Week = "星期日";
    }      
}

看到这段代码的时候,感觉自己要爆炸。功能很简单,把1-7的数字换成星期x,如果我们换种写法。

// Javascript
let weekdayMap = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
this.Week = weekdayMap[value];

是不是好看一些。

上面的这个重构,就是一个用表驱动法替代if-else的例子。除了好看些,表驱动法还有其他优点,最大的优点就是数据和逻辑分离。

平常我们在实践中,会将一些可变的配置项放在配置文件,稍微讲究点,放在数据库也可以。这样做的好处是对于一些行为的调整,我们不需要去动逻辑代码,只要变更配置项。这也是一种数据和逻辑分离的思想。

表驱动法也是这样的。在上述例子中,如果我们需要支持多语言。

// Javascript
let weekdayMap = {
    'zh-cn': ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
    'en-us': ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
};
this.Week = weekdayMap[language][value];

可以看出,在随后的工作中,如果需要支持更多的语言,我们只需要在数据表中添加数据。如果此时还坚持用if-else,得写多少行。

数据可以放到任意地方,可以是配置文件,可以是接口,可以是数据库。修改甚至不一定需要程序介入。

但这个也不是万金油,对于值区间,就不是那么直观的替换。考虑下面例子。

if ($val<100) {
    return '区区几十人';
} elseif ($val < 1000) {
    return '几百号';
} else {
    return '成千上万';
}

此时还想用字典表,就不太现实。有种思路是先将$val处理成离散值,比如数下里面几个0,但这样背离了数据和逻辑分离的原则,区间变了,逻辑也要变。考虑以下实现

$ranges = [
    [null, false, 100, false, '区区几十人'],
    [100, true, 1000, false, '几百号'],
    [1000, true, null, false, '成千上万'],
];
foreach ($ranges as list($start, $startInclude, $end, $endInclude, $ret)) {
    if (!is_null($start) and ($startInclude?$val<$start:$val<=$start)) {
        // start 存在,且不满足
        continue();
    } 
    if (!is_null($end) and ($endInclude?$val>$end:$val>=$end)) {
        continue();
    }
    return $ret;
}
return null;

严格意义上,这不算表格驱动,只是由数据-逻辑分离联想到的一种解法。