从具有多个默认值的缓存中提取代码时会闻到什么味道?

时间:2018-07-08 12:27:20

标签: java caching

我们有一个包含4列的表格:

A B C VALUE  
1 2 0 100  
0 3 3 200  
0 0 7 400  
0 0 0 700  

键由3列组成:A,B和C。
我们将此表保存在HashMap<ImmutableTriple<Integer, Integer, Integer>, Integer>对象上。

零代表默认值,因此在找不到确切密钥的情况下,我们应根据以下逻辑拉出相关密钥:

value = cache.get(new ImmutableTriple<>(a, b, c));
if (value != null) {
    return value;
}
value = cache.get(new ImmutableTriple<>(a, b, 0));
if (value != null) {
    return value;
}
value = cache.get(new ImmutableTriple<>(a, 0, c));
if (value != null) {
    return value;
}
value = cache.get(new ImmutableTriple<>(a, 0, 0));
if (value != null) {
    return value;
}
value = cache.get(new ImmutableTriple<>(0, 0, 0));
if (value != null) {
    return value;
}

通过3个具有默认值的ID的键获取会产生代码异味的问题。有没有更好,更清洁的方法?

2 个答案:

答案 0 :(得分:0)

您可以使用默认值为三胞胎的所有可能状态创建“掩码映射”:

private static List<Integer[]> mask = new ArrayList<>();
static {
    mask.add(new Integer[] {null, null, null});
    mask.add(new Integer[] {0, null, null});
    mask.add(new Integer[] {null, 0, null});
    mask.add(new Integer[] {null, null, 0});
    mask.add(new Integer[] {null, 0, 0});
    mask.add(new Integer[] {0, null, 0});
    mask.add(new Integer[] {0, 0, null});
    mask.add(new Integer[] {0, 0, 0});
}

假设为空,表示必须填充键值。然后,您可以从该掩码创建三元组键的列表:

List<Triple<Integer, Integer, Integer>> triples = mask.stream()
                                                          .map(row -> genrateTriple(row, a, b, c))
                                                          .collect(Collectors.toList());

generateTriplets的代码:

 private Triple<Integer, Integer, Integer> genrateTriple(Integer[] row, int... values) {
    int[] tripleElems = new int[3];
    for (int i = 0; i < row.length; i++) {
        tripleElems[i] = row[i] == null ? values[i] : row[i];
    }
    return new ImmutableTriple<>(tripleElems[0], tripleElems[1], tripleElems[2]);
}

并在搜索中使用它:

return triples.stream()
           .map(triple -> cache.get(triple))
           .findFirst()
           .orElse(null);

但是我不确定这是否比您的“代码气味”;)好。

编辑: 当然,如果您避免创建中间集合(findFirst是短循环运算符),这样做会更有效:

Integer result = mask.stream()
        .map(row -> genrateTriple(row, a, b, c))
        .map(cache::get)
        .findFirst()
        .orElse(null);

答案 1 :(得分:0)

有时候,如果它使代码更具可读性,那么爆炸循环不一定是一件坏事。

我建议您将代码保持原样,因为稍后您会看到,如果尝试压缩它,很可能会遇到更糟糕的情况。如果您坚持要继续阅读。


我的想法很简单:您实际上是试图遍历几个掩码以用于您的匹配模式。在这种情况下,掩码为“是否使用id或默认值”。

  1. 如果没有匹配项,则不做任何掩饰,
  2. 如果没有匹配项,请屏蔽c,
  3. 遮罩b,如果不匹配,
  4. 遮罩b c,如果没有匹配项,
  5. 屏蔽a b c(如果没有匹配项的话)
  6. 返回null。

因此,我们可以使用某种数据结构来存储掩码,如下所示:

    // true = use id (don't mask); false = use default (mask).
    List<ImmutableTriple<Boolean, Boolean, Boolean>> matchMasks = new ArrayList<>();
    matchMasks.add(new ImmutableTriple<>(true, true, true));
    matchMasks.add(new ImmutableTriple<>(true, true, false));
    matchMasks.add(new ImmutableTriple<>(true, false, true));
    matchMasks.add(new ImmutableTriple<>(true, false, false));
    matchMasks.add(new ImmutableTriple<>(false, false, false));

当然,您可以使用List中的ListArray中的Array或任何适合存储此掩码的东西。

现在我们可以使用循环来压缩您的代码:

private Integer match(int a, int b, int c)
{
    Integer value = null;
    // Loop through match rules.
    for (ImmutableTriple<Boolean, Boolean, Boolean> mask : matchMasks)
    {
        // Check whether to apply mask.
        int aId = mask.getLeft() ? a : 0;
        int bId = mask.getMiddle() ? b : 0;
        int cId = mask.getRight() ? c : 0;
        // Try to match cache.
        value = cache.get(new ImmutableTriple<>(aId, bId, cId));
        // Stop if match found.
        if (value != null) break;
    }
    return value;
}

全部归为一类:

public class Test
{
    private HashMap<ImmutableTriple<Integer, Integer, Integer>, Integer> cache = <your table here>;
    private List<ImmutableTriple<Boolean, Boolean, Boolean>> matchMasks = new ArrayList<>();

    public Test()
    {
        // true = use id (don't mask); false = use default (mask).
        this.matchMasks.add(new ImmutableTriple<>(true, true, true));
        this.matchMasks.add(new ImmutableTriple<>(true, true, false));
        this.matchMasks.add(new ImmutableTriple<>(true, false, true));
        this.matchMasks.add(new ImmutableTriple<>(true, false, false));
        this.matchMasks.add(new ImmutableTriple<>(false, false, false));
    }

    private Integer match(int a, int b, int c)
    {
        Integer value = null;
        // Loop through match rules.
        for (ImmutableTriple<Boolean, Boolean, Boolean> mask : this.matchMasks)
        {
            // Check whether to apply mask.
            int aId = mask.getLeft() ? a : 0;
            int bId = mask.getMiddle() ? b : 0;
            int cId = mask.getRight() ? c : 0;
            // Try to match cache.
            value = this.cache.get(new ImmutableTriple<>(aId, bId, cId));
            // Stop if match found.
            if (value != null) break;
        }
        return value;
    }
}

P.S。对于此特定情况,由于0 =默认值,因此您可以为掩码使用1和0而不是true和false来进行int aId = mask.getLeft() * a;,但这很简单。

如您所见,尽管我们确实将代码“压缩”成循环,但其效果还是值得商de的。实际上,我认为该代码实际上不那么可读。因此,再次强调一下,有时候爆炸循环并不一定很糟糕。