表驱动法,逻辑控制优化利器

开发 前端
对简单的情况而言,使用逻辑语句更为容易和直白,但随着逻辑链的越来越复杂,查表法也就愈发显得更具有吸引力。

本文转载自微信公众号「架构精进之路」,作者张张。转载本文请联系架构精进之路公众号。

最近好多同学在开发过程中谈到设计表结构的一些idea,为了让大家少走一些弯路,今天就计划聊聊表驱动法吧~

1、概念介绍

表驱动法 是一种编程模式,从表里查找信息而不使用逻辑语句(if/else)

事实上,凡是能通过逻辑语句来选择的事物,都可以通过查表来选择。

对简单的情况而言,使用逻辑语句更为容易和直白,但随着逻辑链的越来越复杂,查表法也就愈发显得更具有吸引力。

应用原则

适当的情况下,采用表驱动法,所生成的代码会比复杂的逻辑代码更简单,更容易修改,而且效率更高。

2、应用实践

2.1 直接访问

2.1.1 今天周几?

传统写法:

  1. String today = "周日"
  2. Switch( dayForMonth % 7 ){ 
  3.     case 0 :  
  4.         today = "周日"
  5.     case 1 :  
  6.         today = "周一";    
  7.     case 2 : 
  8.         today = "周二";    
  9.     case 3 : 
  10.         today = "周三";    
  11.     case 4 : 
  12.         today = "周四";    
  13.     case 5 : 
  14.         today = "周五";    
  15.     default
  16.         today = "周六";    

表驱动法:

  1. String [] weekday = new String[]{"周日","周一","周二","周三","周四","周五","周六"}; 
  2.   String today = weekday [ dayForMonth % 7 ]; 

2.1.2 每个月多少天?

传统写法:

  1. if(1 == iMonth) { 
  2.   iDays = 31; 
  3. else if(2 == iMonth) { 
  4.   iDays = 28; 
  5. else if(3 == iMonth) { 
  6.   iDays = 31; 
  7. else if(4 == iMonth) { 
  8.   iDays = 30; 
  9. else if(5 == iMonth) { 
  10.   iDays = 31; 
  11. else if(6 == iMonth) { 
  12.   iDays = 30; 
  13. else if(7 == iMonth) { 
  14.   iDays = 31; 
  15. else if(8 == iMonth) { 
  16.   iDays = 31; 
  17. else if(9 == iMonth) { 
  18.   iDays = 30; 
  19. else if(10 == iMonth) { 
  20.   iDays = 31; 
  21. else if(11 == iMonth) { 
  22.   iDays = 30; 
  23. else if(12 == iMonth) { 
  24.   iDays = 31; 

表驱动法:

把逻辑写成 map 或是 list,一目了然,可以搞个2维数组还加上了闰年的逻辑。

  1. const monthDays = [ 
  2.   [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], 
  3.   [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 
  4.  
  5. function getMonthDays(monthyear) { 
  6.   let isLeapYear = (year % 4 === 0) && (year % 100 !== 0 || year % 400 === 0) ? 1 : 0 
  7.   return monthDays[isLeapYear][(month - 1)]; 
  8.  
  9. console.log(getMonthDays(2, 2000)) 

2.2 索引访问

有时只用一个简单的数学运算还无法把 age 这样的数据转换成为表键值,这种情况可以通过索引访问的方法加以解决。

索引应用:先用一个基本类型的数据从一张索引表中查出一个键值,然后在用这一键值查出需要的主数据。

举例:

有100件商品,商店物品编号(范围 0000-9999)

创建两张表:索引表(0-9999),物品(查询)表(0-100)

索引访问有两个优点:

  • 如果主查询表的每条记录都很大,那创建一个浪费了很多空间的数组所用的空间,要比建立主查询表所用的空间小得多。
  • 操作索引中的记录比操作主查询表的的记录更方便,编写到表里面的数据比嵌入代码的数据更容易维护。

2.3 阶梯访问

这种访问方法不像索引结构那样直接,但是它要比索引访问方法节省空间。

阶梯结构的基本思想:表中的记录对于不同数据范围有效,而不是对不同的数据点有效。

举例:

一个等级评定的应用程序,其中“B”记录所对应的范围是 75.0%-90.0%

>= 90.0% A

<90.0% B

<75.0% C

<65.0% D

<50.0% F

这种划分范围用在查询表中是不合适的,因为你不能用简单的数据转换函数来把表键值转换成 A-F 字母所代表的等级。用索引也不合适,因为这里用的是浮点数。

在应用阶梯方法的时候,必须谨慎的处理范围的端点。

  1. Dim rangeLimit() As Double = {50.0, 65.0, 75.0, 90.0, 100.0} 
  2. Dim grade() As String={"F""D""C""B""A"
  3. maxGradeLevel = grade.Length - 1 
  4.  
  5. // assign a grad to a student based on the student's score 
  6. gradeLevel= 0  
  7. studentGrade = ”A"  
  8. while( studentGrade = "A" and gradeLevel < maxGradeLevel )      
  9.   if( studentScore < rangeLimit( gradeLevel ) ) then          
  10.     studentGrade = grade ( gradeLevel)      
  11.   end if     
  12.   gradeLevel = gradeLevel + 1  
  13. wend 

与其他表驱动法相比,这种方法的优点在于它很适合处理那些无规则的数据。

在使用阶梯访问时需要注意的一些细节:

1)留心边界端点

注意边界:< 与 <=,确认循环能够在找出最高一级区间后恰当地终止。

2)考虑用二分查找取代顺序查找

如果列表很大,可以把它替换成一个准二分查找法,从头查找是很耗费性能的

3)考虑用索引访问来取代阶梯访问

阶梯访问中的查找操作可能会比较耗时,如果执行速度很重要,那可以考虑用索引访问来取代阶梯查找,即以牺牲存储空间来换取速度。

2.4 构造查询键值

如上述例子,我们希望能够将数据作为键值直接访问表,这样既简单又快速。

但是问题或者数据通常并不是这样友好,那就需要引出 构造查询键值 的方法。

费率与年龄、性别、婚姻及交费年数等不同情况而变动。

1)复制信息从而能够直接使用键值

age补齐:50 岁以上的年龄都复制一份 50 岁的费率。

这样优点在于表自身结构非常简单那,访问表的逻辑也很简单;

缺点在于复制生成的冗余信息会浪费空间,也即是利用空间换效率。

2)转换键值以使其能够直接使用

费率表查询时,用一个函数将 age 转换为另一个数值。

在此例子中,该函数必须把所有介于 0-5 直接的年龄转换成一个键值,例如 5,同时把所有超过 50 的年龄都转换成另一个键值,例如 50。

这样在检索前可以用 min()和 max()函数来做这一转换。

例如,你可以用下述表达式:max(min(50, age), 17) 来生成一个介于 17-50 之间的表键值。

3)把键值转换提取城独立子程序

如果你必须要构造一些数据来让它们像表键值一样使用,那就把数据到键值的转换操作提取成独立的子程序。这样可避免在不同位置执行了不同的转换,也使得转换操作修改起来更加容易。

任务是个方法,不再是数值了,这里我们可以利用 Dart 这样的支持高阶函数的语言特性,把方法当做一个对象存储在表中。

  1. var data = <String, Map>{ 
  2.     "A": { 
  3.       "name""AA"
  4.       "action": (name) => print(name + "/AA"), 
  5.     }, 
  6.     "B": { 
  7.       "name""BB"
  8.       "action": (name) => print(name + "/BB"), 
  9.     }, 
  10.   }; 
  11.    
  12.   var action = data["A"]["action"]; 
  13.   action("kk"); 

3、总结

1)如何从表中查数据?

  • List item
  • 直接访问
  • 索引访问
  • 阶梯访问

2)在表里存些什么?

  • 数据
  • 动作(action)-描述该动作的代码/该动作的子程序的引用。

表驱动法提供了一种复杂的逻辑和继承结构的替换方案。如果你发现自己对某个应用程序的逻辑或者继承关系感到困惑,那是否可以通过一个查询表来加以简化。

  • 使用表的关键决策是决定如何去访问表,可以采取直接访问、索引访问或阶梯访问
  • 使用表的另一项关键决策是决定如何去把什么内容放入表中
  • 需要保存浮点数和范围数时,使用阶梯访问的形式。

 

责任编辑:武晓燕 来源: 架构精进之路
相关推荐

2022-10-10 08:24:15

编程数据逻辑语句

2023-07-19 12:24:48

C++constexpr​语句

2018-05-23 13:47:28

数据库PostgreSQL查询优化

2011-09-16 13:15:38

SQL Server优化

2010-06-11 14:25:08

通信协议

2010-04-20 11:31:26

Oracle逻辑结构

2011-11-28 10:50:56

JavaJVM优化

2014-03-14 14:03:55

系统优化达梦集群

2009-11-16 10:30:24

Oracle驱动表

2017-12-12 14:26:16

数据库PostgreSQL逻辑优化

2017-10-10 14:56:13

GatherSDN带内控制

2024-09-26 17:45:36

2011-03-21 14:27:15

数据库优化业务逻辑设计

2021-08-17 10:39:54

SQL Server数据库优化

2024-04-03 09:12:03

PostgreSQL索引数据库

2009-08-31 16:29:21

C#控制输入法

2017-11-06 17:16:55

Linux设备驱动并发控制

2017-02-28 17:46:15

Linux驱动技术并发控制

2023-07-24 09:00:00

数据库MyCat

2013-01-25 11:15:56

RHEV
点赞
收藏

51CTO技术栈公众号