在我们平时的开发中,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判断的是不同的范围,这该怎么查?

另一个则是你需要在表里面查询什么,是数据?还是动作?亦或是索引?

基于这两个问题,这里将查询分为以下三种:

直接访问

索引访问

阶梯访问

直接访问表

我们上面介绍的那个日历就是一个很好的直接访问表的例子,但是很多情况并没有这么简单。

统计保险费率

假设你在写一个保险费率的程序,这个费率会根据年龄、性别、婚姻状态等不同情况变化,如果你用逻辑控制结构(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];

}

阶梯访问适合在索引访问无法适用的场景,比如如果是浮点数,就无法用索引访问创建一个数组来拿到索引。

在数据量比较大的情况下,考虑用二分查找来代替顺序查找,。

在大多数情况下,优先使用直接访问和索引访问,除非两者实在无法处理,才考虑使用阶梯访问。

从这三种访问表来看,主要是为了解决如何从表中查询,在不同的场景应该使用合适的访问表。

参考资料:

java表驱动法索引访问_表驱动法 - SegmentFault 思否相关推荐

  1. java大麦_大麦大 - SegmentFault 思否

    通过这个教程,我想告诉你在 React 中如何使用 state 和 effect 这两种 hooks 去请求数据.我们将使用众所周知的 Hacker News API 来获取一些热门文章.你将定义属于 ...

  2. java - websocket配合spring-security使用token认证_个人文章 - SegmentFault 思否

    使用框架介绍 spring boot 1.4.3.RELEASE spring websocket 4.3.5.RELEASE spring security 4.1.3.RELEASE sockjs ...

  3. java 翻转单词_单词翻转 - SegmentFault 思否

    题目来源 <编程之法> 第一章 举一反三 习题 题目描述 输入一个英文句子,翻转句子中单词的顺序.要求单词内字符的顺序不变,句子中单词以空格符隔开.为简单起见,标点符号和普通字母一样处理. ...

  4. python之父去面试-面试题_个人文章 - SegmentFault 思否

    1 Spring Bean是什么? 在spring中由Spring创建和管理的对象称为bean, bean有相关的特性,例如懒加载,作用域,生命周期这些. 懒加载就是延迟加载,启动的时候,会创建所有对 ...

  5. node.js - Nodejs 分布式事务_个人文章 - SegmentFault 思否

    事务是恢复和并发控制的基本单位,保证 ACID:原子性.一致性.隔离性.持久性. 对于全是异步的 Nodejs 而言, 并不适合做事务操作: 代码书写上: try ... catch ... 是写给人 ...

  6. java9 gc log参数迁移_个人文章 - SegmentFault 思否

    序 本文主要研究一下java9 gc log参数的迁移. 统一JVM及GC的Logging java9引进了一个统一的日志框架,对gc log的输出进行了统一的配置. 相关JEP(JDK Enhanc ...

  7. java new url()_Java(4)URL - SegmentFault 思否

    (一)URL类 URL类是java.net包中的一个重要的类.URL由4部分组成:资源类型(协议).存放资源的主机域名(地址).资源文件名和端口. 例如,http://210.45.240.3:80/ ...

  8. python质因子分解_质因子分解_个人文章 - SegmentFault 思否

    质因子分解的问题就是给定一个n使得n能够分解为多个因子的乘积形式,并且相同因子用指数形式表示: 例如180=2^23^25; 对于这个问题,很好理解,我们的目的就是寻找其因子,通常的方法也就是从0开始 ...

  9. python页面转图片_网页转图片_技术分享 - SegmentFault 思否

    将一段文字转成图片,比如常用的长微博工具 这里用 PHP 来处理. text2pic composer require dsgygb/text2pic touch test.php require ' ...

最新文章

  1. 友盟数据—值得手游创业者关注的玩家数据
  2. 机器学习中的三对性能度量参数
  3. 【原】storm源码之一个class解决nimbus单点问题
  4. 从github下载的项目如何运行??---------本文以vue的项目为例
  5. 线性同余法产生均匀随机数C语言,利用线性同余法产生随机数进行同步计算
  6. attachEvent时间监听方式
  7. 【数据结构课设】:后缀树
  8. 纯前端实现—用户注册登录界面
  9. Activity及其生命周期
  10. go 的基本命令详解
  11. python——获取矩形四个角点的坐标
  12. ibm服务器开机显示如何设置,IBM服务器开机进入WEBBIOS界面配置RAID
  13. 如何使用网页版Instagram来发布图片
  14. FOJ 1573 大学自习室
  15. FZU 1573 大学自习室
  16. Adobe Photoshop 2021 22.1.1.138中文版(win/mac)
  17. java当前时间减一年_Java获取时间,将当前时间减一年,减一天,减一个月
  18. 正益移动王国春:布局在是与不是之间
  19. 熬夜学Java语言-内部类种类解读
  20. 20180508----01:15

热门文章

  1. 值得分享!最新发现了10个冷门好用软件,一眼就会爱上
  2. 源码推荐:基于uni-app前端框架,开源版本还开源免费商用
  3. 人是被经验塑造的动物,一家公司也是
  4. 那些我们常用的scrum工具、敏捷开发工具
  5. 在leangoo里怎么设置背景,修改密码?
  6. AGX Xavier GPIO
  7. 如何在电脑中配置jdk环境变量
  8. java 移动平均值_使用用户输入数组移动平均线
  9. 计算机内存不足 ssd,电脑提示内存不足怎么办 虚拟内存设置方法【详解】
  10. xpage 传参_Vuex入门、同步异步 存取值