前言
《代码大全2》中单独罗列了一章描述表驱动法,旨在告诉读者,面对复杂的多分支结构(if 或者是 case),可以想着使用表驱动法实现或者改造。面向对象的多态也是用来改善多 if 或者多 case 的代码结构。诚然,多if 或者多 case 的代码并非十恶不赦,但是也需要看到多态实现和表驱动实现的好处,用于评估改造的成本和收益。工作中曾经也用过类似的思想,这里记录下来。
问题定义
- 存在多分支结构
// 判断一个月有多少天
if (month = 1) {
days = 31
} else if (month = 2) {
days = 28
} else if (month = 3) {
days = 31
} ..... else {
}
- 嵌套 if 结构,用于支撑复杂逻辑
if (女性) {
if (单身) {
if (不抽烟) {
if (年龄 < 18) {
保险费率 = 200
} else if (年龄 < 38) {
保险费率 = 300
} ...
}
}
}
- 解析多种数据格式(规则)。这里也可以使用多态改造,后续会交代表驱动的实现为什么比多态更好
if (规则1) {
规则1解析逻辑块
} else if (规则2) {
规则2解析逻辑块
} else if ... {
}
需求定义
改造代码属于非功能性需求,是优化程序的,让复杂逻辑保持迭代健康的手段。主要的目的是
- 为新需求奠定良好的实现基础
- 明确程序职责,把变化的东西归集到表中,提高拓展性
改造过程
- 存在多分支结构
// 判断一个月有多少天
if (month = 1) {
days = 31
} else if (month = 2) {
days = 28
} else if (month = 3) {
days = 31
} ..... else {
}
这个场景很简单,用一个数组维护即可
int daysPerMonth[] = {0, 31, 28,31, ....}
days = daysPerMonth[input]
- 嵌套 if 结构
if (女性) {
if (单身) {
if (不抽烟) {
if (年龄 < 18) {
保险费率 = 200
} else if (年龄 < 38) {
保险费率 = 300
} ...
}
}
}
不管逻辑再怎么复杂,都能把嵌套的逻辑扁平化,如:
性别 - 是否单身 - 是否抽烟 - 年龄 - 费率
形如数据库中的一条表字段,那么就能启示我们这么做:
保险费率 = rateTable(性别, 是否单身, 是否抽烟, 年龄)
- 解析多种数据格式(规则)
if (规则1) {
规则1解析逻辑块
} else if (规则2) {
规则2解析逻辑块
} else if ... {
}
规则是一种较为复杂的实现,不同的规则可能需要读取不同的字段。简单来说,不同规则所关注的信息类型、范围可能不同。那么针对这些容易变化的场景,表驱动大有用处。
// 规则定义Eg
规则1
总字段数 3
[FiledType] [FiledValue]
字段1 角色 admin
字段2 姓名 james
字段3 参数 {a: hello}
规则1结束
// 有多个规则,都放到数组中
fieldDescription[]
// 规则字段对象表定义 AbstractFiled 是一个抽象类 (这里引入了多态)
AbstractFiled field[]
field[角色] = new 角色解析器 extends AbstractFiled ();
field[姓名] = new 姓名解析器 extends AbstractFiled ();
field[参数] = new 参数解析器 extends AbstractFiled ();
// 规则解析核心代码
while (有待消费的规则事件) {
规则k = fieldDescription[规则事件.规则编号]
n = 1
while (未遍历完规则k的所有字段) {
fieldType = 规则k[字段n].FiledType
fieldValue = 规则k[字段n].FiledValue
解析器 = field[fieldType]
解析器.解析(fieldType, fieldValue)
n++
}
}
可以看到,用了表驱动设计,核心代码块很好看懂,变化的部分都被封装到了不同的类去了。
其中fieldDescription[规则事件.规则编号]
规则k[字段n]
和 field[fieldType]
都是查表的思想
表驱动设计中数据范围的处理
上文提到的查询都可以视为 索引表, 但是索引是一个区间的时候该如何映射到入参呢,《代码大全2》把以下这种解决方案成为 阶梯访问表
- 一个简单的例子
[分数 / 总分 定义] [等级]
>= 90% A
< 90% B
< 75% C
< 65% D
< 50% F
根据分数查等级的SQL
select
等级
from
表
where
自己的分数 / 总分 >= [分数 / 总分 定义]
limit 1
- 同理,更加复杂的例子也可以用这个方法
概率 保险索赔额度
0.45232 0
0.54723 254
0.5323 43535
后记
曾经在学习 Spock 测试框架的时候就了解到了表驱动设计。遗留的问题今天终于理解了。包括工作中遇到的异常节点的规则配置、lookup大宽表、角色权限表查询、多条件计算器实现。举的例子跟公司业务相关就不太好展开描述。简单来说,印证了表驱动设计有足够多的应用场景。使用表驱动设计的时候,想一想能不能用多态,如果代价合理,引入多态是个不错的选择。