表驱动法

在我们日常平凡的开辟中,if else是最经常运用的前提推断语句。在一些简朴的场景下,if else用起来很爽,然则在轻微庞杂一点儿的逻辑中,大批的if else就会让他人看的一脸蒙逼。
如果他人要修正或许新增一个前提,那就要在这个上面继承增添前提。如许恶性轮回下去,底本只要几个if else末了就有能够变成十几个,以至几十个。
别说不能够,我就见过有人在React组件内里用了大批的if else,可读性和可维护性异常差。(固然,这个不算if else的锅,主如果组件设想的题目)

这篇文章重要介入自《代码大全2》,原书中运用vb和java完成,这里我是基于TypeScript的完成,对书中内容加入了一些本身的明白。

从一个例子提及

日历

如果我们要做一个日历组件,那我们肯定要知道一年12个月中每月都若干天,这个我们要怎样推断呢?
最笨的要领固然是用if else啊。

if (month === 1) {
    return 31;
}
if (month === 2) {
    return 28;
}
...
if (month === 12) {
    return 31;
}

如许一会儿就要写12次if,白白糟蹋了那末多时刻,效力也很低。
这个时刻就会有人想到用switch/case来做这个了,然则switch/case也不会比if简化许多,依旧要写12个case啊!!!以至如果还要斟酌闰年呢?岂不是更贫苦?

我们无妨转换一下头脑,每月份对应一个数字,月份都是按递次的,我们是不是能够用一个数组来贮存天数?到时刻用下标来接见?

const month: number = new Date().getMonth(),
    year: number = new Date().getFullYear(),
    isLeapYear: boolean = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;

const monthDays: number[] = [31, isLeapYear ? 29 : 28, 31, ... , 31];
const days: number = monthDays[month];

观点

看完上面的例子,相信你对表驱动法有了肯定地熟悉。这里援用一下《代码大全》中的总结。

表驱动法就是一种编程形式,从表内里查找信息而不运用逻辑语句。事实上,通常能经由过程逻辑语句来挑选的事物,都能够经由过程查表来挑选。对简朴的状况而言,运用逻辑语句更加轻易和直白。但随着逻辑链的愈来愈庞杂,查表法也就愈发显得更具吸引力。

运用表驱动法前须要思索两个题目,一个是怎样从表中查询,毕竟不是一切场景都像上面那末简朴的,如果if推断的是差别的局限,这该怎样查?
另一个则是你须要在表内里查询什么,是数据?照样行动?亦或是索引?
基于这两个题目,这里将查询分为以下三种:

  1. 直接接见
  2. 索引接见
  3. 门路接见

直接接见表

我们上面引见的谁人日历就是一个很好的直接接见表的例子,然则许多状况并没有这么简朴。

统计保险费率

假定你在写一个保险费率的顺序,这个费率会依据岁数、性别、婚姻状况等差别状况变化,如果你用逻辑掌握构造(if、switch)来示意差别费率,那末会异常贫苦。

if (gender === 'female') {
    if (hasMarried) {
        if (age < 18) {
            //
        } else if (age < 65) {
            //
        } else {
            // 
        }
    } else if (age < 18) {
        //
    } else if (age < 65) {
        //
    } else if {
        //
    }
} else {
    ...
}

然则从上面的日历例子来看,这个岁数倒是个局限,不是个牢固的值,没法用数组或许对象来做映照,那末该怎样办呢?这里触及到了上面说的题目,怎样从表中查询?
这个题目能够用门路接见表和直接接见表两种要领来处置惩罚,门路接见这个后续会引见,这里只说直接接见表。
有两种处置惩罚要领:
1、复制信息从而能够直接运用键值

我们能够给1-17岁数局限的每一个岁数都复制一份信息,然后直接用age来接见,同理对其他岁数段的也都一样。这类要领在于操纵很简朴,表的构造也很简朴。但有个瑕玷就是会糟蹋空间,毕竟生成了许多冗余信息。

2、转换键值
我们无妨再换种思绪,如果我们把岁数局限转换成键呢?如许就能够直接来接见了,唯一须要斟酌的题目就是岁数怎样转换为键值。
我们固然能够继承用if else完成这类转换。前面已说过,简朴的if else是没什么题目的,表驱动只是为了优化庞杂的逻辑推断,使其变得更天真、易扩大。

enum genders {
    lessThan18 = '<18',
    between18And56 = '18-65',
    moreThan56 = '>65'
}
enum genders {
    female = 0,
    male = 1
}
enum marry = {
    unmarried = 0,
    married = 1
}
const age2key = (age: number): string => {
    if (age < 18) {
        return genders.lessThan18
    }
    if (age < 65) {
        return genders.between18And56
    }
    return genders.moreThan56
}
const premiumRate: {
    [genders: string]: {
        [marry: string]: {
            rate: number
        }
    }
} = {
    [genders.lessThan18]: {
        [genders.female]: {
            [marry.unmarried]: {
                rate: 0.1
            },
            [marry.married]: {
                rate: 0.2
            }
        },
        [genders.male]: {
            [marry.unmarried]: {
                rate: 0.3
            },
            [marry.married]: {
                rate: 0.4
            }
        }
    },
    [genders.between18And56]: {
        [genders.female]: {
            [marry.unmarried]: {
                rate: 0.5
            },
            [marry.married]: {
                rate: 0.6
            }
        },
        [genders.male]: {
            [marry.unmarried]: {
                rate: 0.7
            },
            [marry.married]: {
                rate: 0.8
            }
        }
    },
    [genders.moreThan56]: {
        [genders.female]: {
            [marry.unmarried]: {
                rate: 0.5
            },
            [marry.married]: {
                rate: 0.6
            }
        },
        [genders.male]: {
            [marry.unmarried]: {
                rate: 0.7
            },
            [marry.married]: {
                rate: 0.8
            }
    }
}
const getRate = (age: number, hasMarried: 0 | 1, gender: 0 | 1) => {
     const ageKey: string = age2key(age);
     return premiumRate[ageKey]
        && premiumRate[ageKey][gender]
        && premiumRate[ageKey][gender][hasMarried]
}

索引接见表

我们前面谁人保险费率题目,在处置惩罚岁数局限的时刻很头疼,这类局限每每不像上面那末轻易获得key。
我们当时提到了复制信息从而能够直接运用键值,然则这类要领糟蹋了许多空间,由于每一个岁数都邑保留着一份数据,然则如果我们只是保留索引,经由过程这个索引来查询数据呢?
假定人刚出生是0岁,最多能活到100岁,那末我们须要建立一个长度为101的数组,数组的下标对应着人的岁数,如许在0-17的每一个岁数我们都贮存'<18’,在18-65贮存’18-65′, 在65以上贮存’>65’。
如许我们经由过程岁数就能够拿到对应的索引,再经由过程索引来查询对应的数据。
看起来这类要领要比上面的直接接见表更庞杂,然则在一些很难经由过程转换键值、数据占用空间很大的场景下能够尝尝经由过程索引来接见。

const ages: string[] = ['<18', '<18', '<18', '<18', ... , '18-65', '18-65', '18-65', '18-65', ... , '>65', '>65', '>65', '>65']
const ageKey: string = ages[age];

门路接见表

一样是为了处置惩罚上面谁人岁数局限的题目,门路接见没有索引接见直接,然则会更节约空间。
为了运用门路要领,你须要把每一个区间的上限写入一张表中,然后经由过程轮回来搜检岁数地点的区间,所以在运用门路接见的时刻肯定要注意搜检区间的端点。

const ageRanges: number[] = [17, 65, 100],
  keys: string[] = ['<18', '18-65', '>65'],
  len: number = keys.length;
const getKey = (age: number): string => {
  for (let i = 0; i < len; i++) {
    console.log('i', i)
    console.log('ageRanges', ageRanges[i])
    if (age <= ageRanges[i]) {
      return keys[i]
    }
  }
  return keys[len-1];
}

门路接见适合在索引接见没法实用的场景,比方如果是浮点数,就没法用索引接见建立一个数组来拿到索引。
在数据量比较大的状况下,斟酌用二分查找来替代递次查找,。
在大多数状况下,优先运用直接接见和索引接见,除非二者着实没法处置惩罚,才斟酌运用门路接见。

从这三种接见表来看,主如果为了处置惩罚怎样从表中查询,在差别的场景应当运用适宜的接见表。

参考资料:

  1. 代码大全(第2版)
    原文作者:尹光耀
    原文地址: https://segmentfault.com/a/1190000017959010
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞