用`表驱动`对代码里的if-else来一次`马杀鸡`

什么是表驱动编程

表驱动 是在表中查找信息,而不使用 if-else 或者 switch 语句。

表驱动-马杀鸡

往往我们追求的简单逻辑就是使用逻辑语句直接描述流程,但是往往随着逻辑语句的复杂,使得我们需要考虑用表驱动替代

例如 if-else 和 switch

    if (type == 1) {
        create();
    } else if (type == 2) {
        update();
    } else if (type == 3) {
        query();
    } else {
        delete();
    }
switch(type) {
    case 1:
        create();
        break;
    case 2:
        update();
        break;
    case 3:
        query();
        break;
    default:
        delete();
        break;
}

这两种方法充斥着大量的逻辑判断,也就是 逻辑 & 数据 混杂。而表驱动的方式 核心就是解构 逻辑 与 数据,将 代码 和 数据 划分清楚。

而表驱动的编程方式包含了访问方式有两种

  • 直接通过索引访问
static Runnable[] runnables = {
        () -> {create();},
        () -> {update();},
        () -> {query();},
        () -> {delete();}
        };

public static void main(String[]args){
    runnables[0].run();
}
  • 间接通过索引访问
Map<Integer, Runnable> runnables = new HashMap<>{
    put(1, this::create);
    put(2, this::update);
    put(3, this::query);
    put(4, this::delete);
}
public static void main(String[]args){
    int step = 1;
    runnables.get(step).run();
}

从上面可以看出,我们借助表驱动的方式,不管是直接还是间接都消除了大篇幅的 if-else 或 switch 语句。而且再后续的业务变更的话,我们只需要更改 runnables 数据部分,这样实现了 逻辑 与 数据 的分离。

阶梯访问的表驱动

上面的 if-else 或 switch 还是比较简单的,但是也存在 if-else-if-else , 像这种阶梯阶段的模式

if (score > 80) {
    return veryGood();
} else if (score > 60) {
    return good();
} else {
    return bad();
}

我们在转化该种 代码 和 数据 的时候,往往是先从最大的数值开始的,这样的话,我们不能像上面一样直接用索引做映射,需要额外多一层转化

int[] scores = {80, 60};
Supplier[] methods = {this::veryGood, this::good};

for(int i = 0; i < scores.length; i++) {
    if(scores[i] > score) {
        return methods[i].get();
    }
    return bad();
}

回头我们再看这种转换,确实做到了 代码 和 数据 的分割,不太常见但是确实是表驱动实现的一种方式。因为我们重新定义了数据。

阶梯式表驱动 升华

回溯上面的的阶梯访问的表驱动,仔细阅读一下是否存在问题呢?

  • 大量数据环境下,因为是线性访问,查询成本较高。【不像间接索引实现的访问使用 Map 】
  • 需要手动保证 key 到 索引,索引再到 value 的映射 【从大到小(当前的实现下)】

如果要解决以上的问题,我们可以参考上面间接访问的采用Map,因为考虑要有序,所以我们可以联想到使用 TreeMap, 它是 key-value 结构且有序。 深究 TreeMap, 其上层接口是 NavigableMap, NavigableMap 继承了 SortedMap, 从TreeMap接口的注释能看出,

// A Red-Black tree based NavigableMap implementation. 
// The map is sorted according to the natural ordering of its keys, or by a Comparator provided at map creation time, depending on which constructor is used.

TreeMap 是基于一个平衡二叉树的实现。 因为其具体实现了 NavigableMap, 看 lowerEntry 方法,是以二分查找法查找一个小于或等于 keyEntry, 这样就解决了上述的大数据量的查找问题。 而且因为是 key-value 模式, 我们不需要隐性的手动去维护索引关系。

    /**
     * Returns a key-value mapping associated with the greatest key
     * strictly less than the given key, or {@code null} if there is
     * no such key.
     *
     * @param key the key
     * @return an entry with the greatest key less than {@code key},
     *         or {@code null} if there is no such key
     * @throws ClassCastException if the specified key cannot be compared
     *         with the keys currently in the map
     * @throws NullPointerException if the specified key is null
     *         and this map does not permit null keys
     */
    Map.Entry<K,V> lowerEntry(K key);

所以我们再一次优化后, 这样我们参照 通过间接索引访问,将 阶梯式表驱动访问 也使用 TreeMap 改造了,从结果看,这种比上面线性访问,更能体现 数据 和 代码 分离的效果.

TreeMap<Integer, Supplier> methodMaps = new TreeMap<> {{
        put(80, this::veryGood);
        put(60, this::good);
}}

methodMaps.lowerEntry(score).getValue().get();

结语

瑞思拜!Enjoy!

comments powered by Disqus