代码大全2 - 表驱动法

3,267 阅读5分钟

最近入手了 代码大全2,真是程序员的圣经啊,内容好高端大气上档次。书中建议的初级程序员看的章节就是:表驱动法,真尼玛 NB,以前听都没听过,但是细细一想设计真是精妙呀!其想法绝对不是一个人通过自我学习能醒悟出来的,所以看书啊,尤其是看经典书籍真是提高代码质量的不二法则啊~

表驱动法 的思路是什么:用表来代替那些 if/else 的业务逻辑,你把业务逻辑写在 if/else 里就是硬编码,但是写到表里是可以实现功能分离的,逻辑表可以统一做路径设置或者其他配置,这样的话业务逻辑会变得非常容易阅读和理解了

说白了就是:好看、好改、好配置、好给人讲、好装逼

我不说原理,书光说原理了,有限的代码还是 VB 的,压根没法看,所以我给大家举些例子,大家看代码体会下,思想得落地才能变成自己的,为我所用,如臂指使,指哪打哪,得道升仙


今天周几

像几天周几这样的,其实最适合做表驱动了,因为逻辑最清晰,简单,一个数组+下标,轻轻松松搞定

传统写法:

String today = "周日";
Switch( dayForMonth % 7 ){
    case 0 : 
        today = "周日";
    case 1 : 
        today = "周一";   
    case 2 :
        today = "周二";   
    case 3 :
        today = "周三";   
    case 4 :
        today = "周四";   
    case 5 :
        today = "周五";   
    default:
        today = "周六";   
}

表驱动法

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

每个月对应多少天

传统的写法,我们要写一串 if/else 返回数据

传统写法:

if(1 == iMonth) {iDays = 31;}
  else if(2 == iMonth) {iDays = 28;}
  else if(3 == iMonth) {iDays = 31;}
  else if(4 == iMonth) {iDays = 30;}
  else if(5 == iMonth) {iDays = 31;}
  else if(6 == iMonth) {iDays = 30;}
  else if(7 == iMonth) {iDays = 31;}
  else if(8 == iMonth) {iDays = 31;}
  else if(9 == iMonth) {iDays = 30;}
  else if(10 == iMonth) {iDays = 31;}
  else if(11 == iMonth) {iDays = 30;}
  else if(12 == iMonth) {iDays = 31;}

但是表驱动法就不一样了,把逻辑写成 map 或是 list,一目了然,这里我们搞个2维数组还加上了闰年的逻辑

表驱动法

const monthDays = [
  [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
  [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
]

function getMonthDays(month, year) {
  let isLeapYear = (year % 4 === 0) && (year % 100 !== 0 || year % 400 === 0) ? 1 : 0
  return monthDays[isLeapYear][(month - 1)];
}

console.log(getMonthDays(2, 2000))

不同条件执行不同任务

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

表驱动法

  var data = <String, Map>{
    "A": {
      "name": "AA",
      "action": (name) => print(name + "/AA"),
    },
    "B": {
      "name": "BB",
      "action": (name) => print(name + "/BB"),
    },
  };
  
  var action = data["A"]["action"];
  action("kk");
  
  // if( action is Function ) action("KK");

action 可能会包红线,提示不是方法类型,大家强转一下就行了。这个内部可以无限的往下一层层嵌套,只要保证是 map 的就能通过[]统一书写调用了


加减法

加法和减法有不同的计算共公式,那么就像下面这样写,抽象方法当做对象使用,存到 map 表里去

传统写法:

def test(c,a,b):
    if c == '+':
        return a + b
    elif c == '-':
        return a - b

表驱动法:

def add(a,b):
    return a+b

def minus(a,b):
    return a - b

func_dict = {'+':add,'-':minus}
print(func_dict['+'](1,2))

复杂多条件判断

这个例子逻辑最复杂,因为判断的条件多,并且还有不同配置,像这种问题,我们一般用2维数组来做,x 轴是判断条件,y 轴是不同配置

传统写法:

if( (a && !c ) || (a && b && c)){
    category = 1 ;     
}else if( (b && ! a) || (a && c && !b)){
    category = 2 ;     
}else if ( c && !a && !b){
    category = 3 ;     
}else {
    category = 0 ;     
}

表驱动法:

// 把逻辑变为 2维数组,做好注释,这样看是不是很清晰啊
static int categoryTable[2][2]={
    //!b!c   !bc   b!c   bc
        0,   3,     2,    2, // !a    
        1,   2,     1,    1, // a
}
// 使用
category = categoryTable[1][0];

带取值范围的

像某某范围内是啥这样的,我们取两边的端点数值作为依据,在数值判断时使用最简单的方式:for 循环

  • 0-59 分是不及格 F级
  • 60- 79 是及格 E级
  • 80-84 是普通 D级
  • 85-89 是良好 C级
  • 90 - 94 是优秀 B级
  • 95-100 是太棒了 A级

表驱动法:

int [] grade = {59,79,84,89,94,100}; 
String [] level = {"F","E","D","C","B","A"},

public String getLevel (int grade){
    for( int i = 0 ; i < grade.length ; i++){
        if(grade <= grade[i]){
            return  level[i];
        }
    }
}

Map 里插入处理方法的

很多时候我们有一种条件,就有一种 Function 处理方法,为此我们试用大量 if、else 来承载这些逻辑判断,但是这些 if、else 是写在逻辑方法里的,查找、阅读、修改起来不是很方便,也不够清晰,于是就有了用表驱动法的思路改省略这些 if、else

思路就是在支持高阶函数的语言里,把 Function 看做对象插入到 map 里,多了不说,大家看个例子,例子用 Dart 写的,这是修改的一个 Flutter 动画 API

无关代码都去掉了

enum FreeSlideTransitionMode {
  reverseX,
  reverseY,
}

class FreeSlideTransition extends AnimatedWidget {

  // x,y 轴反转播放时数据有不同的处理方式,用 map 承载
  Map<FreeSlideTransitionMode, Function(Animation animation, Offset offset)> worksMap = {
  
    // x 轴反转操作
    FreeSlideTransitionMode.reverseX: (Animation animation, Offset offset) {
      if (animation.status == AnimationStatus.reverse) {
        return offset = Offset(-offset.dx, offset.dy);
      }
      return offset;
    },
    
    // y 轴反转操作
    FreeSlideTransitionMode.reverseY: (Animation animation, Offset offset) {
      if (animation.status == AnimationStatus.reverse) {
        return offset = Offset(offset.dx, -offset.dy);
      }
      return offset;
    },
  };

  @override
  Widget build(BuildContext context) {
    var offset = animation.value;
    
    // 具体试用就是这样
    offset = worksMap[type](animation, offset);
  }
}

这样的话,map 内含核心处理逻辑写在最外面,阅读修改都很方便,基本可以实现一看就懂的设计追求