Java 使用多个默认值从缓存中提取时的代码气味?

Java 使用多个默认值从缓存中提取时的代码气味?,java,caching,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对象上 零表示默认值,因此如果找不到确切的键,则应根据以下逻辑拉取相关键: value = cache.get(new ImmutableTriple<>(a, b, c)); if (value != null) { return value; } value =

我们有一个包含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
对象上

零表示默认值,因此如果找不到确切的键,则应根据以下逻辑拉取相关键:

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;
}
value=cache.get(新的不可变三元组(a、b、c));
if(值!=null){
返回值;
}
value=cache.get(新的不可变三元组(a、b、0));
if(值!=null){
返回值;
}
value=cache.get(新的不可变三元组(a,0,c));
if(值!=null){
返回值;
}
value=cache.get(新的ImmutableTriple(a,0,0));
if(值!=null){
返回值;
}
value=cache.get(新的ImmutableTriple(0,0,0));
if(值!=null){
返回值;
}
问题是,通过使用默认值的3个ID的键获取会产生代码气味。有没有更好\更干净的方法可以做到这一点?

您可以使用默认值为三元组的所有可能状态创建一个“掩码映射”,例如:

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});
}
但我不确定这是否比你的“代码气味”更好)

编辑: 当然,如果避免创建中间集合(findFirst是一个简短的circut运算符),这将更加有效:


有时,如果分解循环使代码更具可读性,那么它并不一定是一件坏事

我建议您保持代码的原样,因为您马上就会看到,如果您试图压缩代码,很可能会导致更糟糕的结果。如果你坚持,继续阅读


我的想法很简单:您实际上是在尝试循环通过几个遮罩来用于匹配模式。本例中的掩码是“是使用id还是默认”

  • 如果没有匹配项,则不隐藏任何内容
  • 掩码c,如果不匹配
  • 掩码b,如果不匹配
  • 掩码b c,如果不匹配
  • 屏蔽a b c,如果不匹配
  • 返回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));
    
    全部放在一个类中:

    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;
        }
    }
    
    公共类测试
    {
    私有HashMap缓存=;
    私有列表匹配掩码=新的ArrayList();
    公开考试()
    {
    //true=使用id(不屏蔽);false=使用默认值(屏蔽)。
    this.matchMasks.add(新的ImmutableTriple(true,true,true));
    this.matchMasks.add(新的ImmutableTriple(true,true,false));
    this.matchMasks.add(新的ImmutableTriple(true、false、true));
    this.matchMasks.add(新的ImmutableTriple(true、false、false));
    this.matchMasks.add(新的ImmutableTriple(false,false,false));
    }
    私有整数匹配(整数a、整数b、整数c)
    {
    整数值=null;
    //循环检查匹配规则。
    for(不可变三重掩码:this.matchMasks)
    {
    //检查是否应用遮罩。
    int-aId=mask.getLeft()?a:0;
    int bId=mask.getMiddle()?b:0;
    int-cId=mask.getRight()?c:0;
    //尝试匹配缓存。
    value=this.cache.get(新的不可变三元组(aId、bId、cId));
    //如果找到匹配项,请停止。
    如果(值!=null)中断;
    }
    返回值;
    }
    }
    
    另外,对于这种特殊情况,由于0=默认值,您可以使用1和0代替掩码的true和false,并执行以下操作
    int-aId=mask.getLeft()*a,但这并不重要


    正如您所看到的,尽管我们将代码“压缩”到一个循环中,但其效果仍有争议。事实上,我认为这段代码实际上可读性较差。因此,有时坚持使用分解循环并不一定不好。

    为三重标识符创建一个自定义类并在
    equals
    方法中包含匹配逻辑不是更好吗?@JoakimDanielson但如果相同的代码仍然存在,只需重写equals方法,这会使它更好吗?我的意思是,代码气味仍然存在,就在其他地方,对吗?你不必创建5个对象,只需创建一个,因为所有匹配逻辑都在
    equals
    方法中,所以对我来说这将是一个改进
    return triples.stream()
               .map(triple -> cache.get(triple))
               .findFirst()
               .orElse(null);
    
    Integer result = mask.stream()
            .map(row -> genrateTriple(row, a, b, c))
            .map(cache::get)
            .findFirst()
            .orElse(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));
    
    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;
        }
    }