表驱动法是一种编程模式(scheme),从表里面查找信息而不使用逻辑语句(if 和 case)。事实上,凡是能够通过逻辑语句来选择的事务,都可以通过查表来选择。对简单的情况而言,使用逻辑语句更为容易和直白。但随着逻辑链的越来越复杂,查表法也就愈发显得更具有吸引力。
18.1 General Considerations in Using Table-Driven Methods
在适当的环境下,采用表驱动法,所生成的代码会比复杂的逻辑代码更简单、更容易修改,而且效率更高。假设你希望把字符划分成字母、标点符号和数字三类,那么你也许会用到下面这种复杂的逻辑链:
1 | if ((('a' <= inputChar) && ( inputChar <= 'z' )) || (( 'A' <= inputChar ) && ( inputChar <= 'Z'>))) { |
但是如果使用查询表的话,就可以把每个字符的类型保存在一个用字符编码访问的数组里:
1 | charType = charTypeTable[inputChar]; |
表驱动法使得我们能够把程序中的信息存放在数据里而不是逻辑里。
Two Issues in Using Table-Driven Methods
在使用表驱动法的时候,必须要解决两个问题。首先,你必须要回答怎样从表中查询条目的问题。另一个问题是,某些数据可能很难直接用于查表,例如长达 9 位的社保号,可能会导致表中存放过多的数据。表驱动法中包含三种查询记录的方法:
- 直接访问(Direct access)
- 索引访问(Indexed access)
- 阶梯访问(Stair-step access)
18.2 Direct Access Tables
和所有的查询表一样,直接访问表代替了更为复杂的逻辑控制结构。之所以说它们是“直接访问”的,是因为你无需绕很多复杂的圈子就能够在表里面找到你想要的信息。下面存储每个月份天数的表就是一种直接访问表:
1 | days_per_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] |
18.3 Indexed Access Tables
有的时候,只用一个简单的数学运算把输入数据转换成为表达键值。这类情况中的一部分可以通过使用索引访问的办法加以解决。当你使用索引的时候,先用一个基本类型的数据从一张索引表中查出一个键值,然后再用这一键值查出你感兴趣的主数据。
假设你经营着一家商店,有大约 100 种商品。再假设每种商品都有一个四位数字的武平编号,其范围是 0000 到 9999。在这种情况下,如果你想用这个编号作为兼职直接查询一张描述商品信息的表,那么就要生成一个具有 10000 条记录的索引素组。该数组中除了与你商店中货物的标志相对应的 100 条记录以外,其余记录都是空的。索引访问技术有两个主要优点:首先,如果主查询表找的每一条记录都很大,那么创建一个浪费了很多空间的索引数组所用的空间,就要比创建一个浪费了很多空间的主查询表所用的空间小得多。其次,即使你用了索引后没有节省内存空间,操作位于索引中的记录有时也比操作位于主表中的记录更方便更廉价。
18.4 Stair-Step Access Tables
还有另外一种访问表的方法,那就是阶梯访问。这种访问方法不像索引结构那样直接,但是它比索引访问方法节省空间。阶梯结构的基本思想是,表中的记录对于不同的数据范围有效,而不是对不同的数据点有效。下面是一个通过阶梯访问表获取学生成绩等级的例子:
1 | range_limit = [50.0, 65.0, 75.0, 90.0, 100.0] |
CHECKLIST
- 你考虑过把表驱动法作为复杂逻辑的替换方案吗?
- 你考虑过把表驱动法作为复杂继承结构的替换方案吗?
- 你考虑过把表数据存储在外部并在运行期间读入,以便在不修改代码的情况下就可以改变这些数据吗?
- 如果无法用一种简单的数组索引去访问表,那么你把计算访问键值的功能提取成单独的子程序,而不是在代码中重复的计算键值吗?
Key Point
- 表提供了一种复杂的逻辑和继承结构的替换方案。如果你发现自己对某个应用程序的逻辑或者继承树关系感到困惑,那么问问自己它是否可以通过一个查询表来加以简化。
- 使用表的一项关键决策是决定如何去访问表。你可以采取直接访问、索引访问或者阶梯访问。
- 使用表的另一项关键决策是决定应该把什么内容放入表中。