【问题标题】:Enum based table/matrix in JavaJava中基于枚举的表/矩阵
【发布时间】:2016-07-24 07:04:57
【问题描述】:

我有两个枚举:level 有 3 个值,criticality 有 4 个值。这两个映射的组合是来自priority 枚举的 8 个值之一。映射是非线性的,将来可能会发生变化。

实现获取级别和关键性并输出优先级的静态函数的最佳*方法是什么?

*最佳易于阅读和理解,易于更改且安全可靠,而且不会影响性能。考虑到输入域将来可能发生变化的解决方案的额外积分。

目前我考虑的方式:

嵌套开关..case。许多行和大量样板代码。如果您忘记在案例中返回值,也容易出错。 基本上代码如下所示:

    switch (bc) {
        case C1:
            switch (el) {
                case E1:
                    return EmergencyPriority.P1;
                case E2:
                    return EmergencyPriority.P2;
                case E3:
                    return EmergencyPriority.P3;
            }
        case C2:
            switch (el) {
                case E1:
                    return EmergencyPriority.P2;
                case E2:
                    return EmergencyPriority.P3;
                case E3:
                    return EmergencyPriority.P4;
            }
        case C3:
            switch (el) {
                case E1:
                    return EmergencyPriority.P4;
                case E2:
                    return EmergencyPriority.P5;
                case E3:
                    return EmergencyPriority.P6;
            }
        case C4:
            switch (el) {
                case E1:
                    return EmergencyPriority.P6;
                case E2:
                    return EmergencyPriority.P7;
                case E3:
                    return EmergencyPriority.P8;
            }
    }

Mutikey Map 需要一个外部库,我还没有找到一种方法来很好地插入初始值,而无需许多函数调用和样板复合键。


if..else if..else 与 switch case 基本相同,但具有更多样板代码。不过不太容易出错。


二维数组当使用枚举值作为数组索引的整数时,如果位置枚举值发生变化,您可能会默默地失败。


您的解决方案在这里

【问题讨论】:

    标签: java matrix enums


    【解决方案1】:

    这种结构可能是存储数据的“最佳”方式(“最佳”= 我假设您的后继,因为我会非常适合您基于 switch 的解决方案)

    1。基于 EnumMap 的解决方案

    EnumMap<Level, EnumMap<Criticality, Priority>> map = new EnumMap<>(Level.class);
    EnumMap<Criticality, Priority> c1 = new EnumMap<>(Criticality.class);
    c1.put(Criticality.E1, Priority.P1);
    ..
    map.put(Level.C1, c1);
    ...
    

    然后,只需编写此实用程序方法即可访问该结构:

    public static Priority priority(Level level, Criticality criticality) {
        return map.get(level).get(criticality);
    }
    

    EnumMap 的优点是:它提供了Map 的便利性,同时相当高效,因为所有可能的键都是预先知道的,所以值可以存储在Object[] 中。

    2。基于数组的解决方案

    您已经提到了这一点,但我仍然会重复这个想法,因为我过去曾这样做过,并且格式正确(当然,开发人员绝对不能破坏),这种方法非常可读且不易出错。

    请记住,格式是这里的关键:

    Priority[][] map = {
      //               Criticality.E1   Criticality.E2   Criticality.E3
      // ----------------------------------------------------------------
      /* Level.C1 */ { Priority.P1    , Priority.P2    , Priority.P3    },
      /* Level.C2 */ { Priority.P2    , Priority.P3    , Priority.P4    },
      /* Level.C3 */ { Priority.P3    , Priority.P4    , Priority.P5    },
      /* Level.C4 */ { Priority.P4    , Priority.P5    , Priority.P6    }
    };
    

    现在,该方法如下所示:

    public static Priority priority(Level level, Criticality criticality) {
        return map[level.ordinal()][criticality.ordinal()];
    }
    

    为了防止在有人在中间添加新枚举值时静默失败,只需添加一个单元测试,为每个枚举文字断言预期的序数。相同的测试还可以断言 Level.values().lengthCriticality.values().length 值,您对未来是安全的。

    【讨论】:

    • 有趣。我第一次听说 EnumMap。但是,我认为这个解决方案乍一看很难阅读,因为您必须阅读三行完整的代码才能看到一个连接。此外,如果两个输入枚举之一被扩展并且未映射的值作为参数给出,则此解决方案将失败并出现运行时错误。
    • @HubertGrzeskowiak:我告诉过你,我对你的基于开关的解决方案完全没问题 :)(IDE 支持缺少 case 语句)。您没有指定在缺少映射的情况下会发生什么。
    • @HubertGrzeskowiak:好的,我添加了另一个支持基于数组的解决方案的论点。
    【解决方案2】:

    1.使用构造函数定义枚举

    受到EnumMap 使用Object[] 的评论的启发,我想出了这个解决方案:

    public enum EmergencyPriority {
        P1(BusinessCriticality.C1, EmergencyLevel.E1), 
        P2(BusinessCriticality.C1, EmergencyLevel.E2, 
           BusinessCriticality.C2, EmergencyLevel.E1), 
        P3(BusinessCriticality.C1, EmergencyLevel.E3, 
           BusinessCriticality.C2, EmergencyLevel.E2), 
        P4(BusinessCriticality.C2, EmergencyLevel.E3, 
           BusinessCriticality.C3, EmergencyLevel.E1), 
        P5(BusinessCriticality.C3, EmergencyLevel.E2), 
        P6(BusinessCriticality.C3, EmergencyLevel.E3, 
           BusinessCriticality.C4, EmergencyLevel.E1), 
        P7(BusinessCriticality.C4, EmergencyLevel.E2), 
        P8(BusinessCriticality.C4, EmergencyLevel.E3);
    
        private static EmergencyPriority[][] PRIORITIES;
    
        private EmergencyPriority(BusinessCriticality c, EmergencyLevel l) {
            addPriority(l, c, this);
        }
    
        private EmergencyPriority(BusinessCriticality c, EmergencyLevel l, 
                BusinessCriticality c2, EmergencyLevel l2) {
            addPriority(l, c, this);
            addPriority(l2, c2, this);
        }
    
        private static void addPriority(EmergencyLevel l, BusinessCriticality c, EmergencyPriority p) {
            if (PRIORITIES == null) {
                PRIORITIES = new EmergencyPriority[EmergencyLevel.values().length][BusinessCriticality.values().length];
            }
            PRIORITIES[l.ordinal()][c.ordinal()] = p;
        }
    
        public static EmergencyPriority of(BusinessCriticality c, EmergencyLevel l) {
            return PRIORITIES[l.ordinal()][c.ordinal()];
        }
    }
    

    2。定义一个枚举并静态初始化对应数组

    另一种解决方案是使用没有构造函数的简单枚举并静态初始化优先级数组,这样可以根据您认为适合的可读性对它们进行重新排序:

    import static com.package.BusinessCriticality.*;
    import static com.package.EmergencyLevel.*;
    
    public enum EmergencyPriority {
        P1, P2, P3, P4, P5, P6, P7, P8;
    
        private static EmergencyPriority[][] PRIORITIES = new EmergencyPriority[BusinessCriticality.values().length][EmergencyLevel.values().length];
    
        private void define(BusinessCriticality c, EmergencyLevel e) {
            PRIORITIES[c.ordinal()][e.ordinal()] = this;
        }
    
        static {
            P1.define(C1, E1);
    
            P2.define(C1, E2);
            P2.define(C2, E1);
    
            P3.define(C1, E3);
            P3.define(C2, E2);
    
            P4.define(C2, E3);
            P4.define(C3, E1);
    
            P5.define(C3, E2);
    
            P6.define(C3, E3);
            P6.define(C4, E1);
    
            P7.define(C4, E2);
    
            P8.define(C4, E3);
        }
    
        public static EmergencyPriority of(BusinessCriticality c, EmergencyLevel e) {
            return PRIORITIES[c.ordinal()][e.ordinal()];
        }
    }
    

    如果您扩展 BusinessCritality 或 EmergencyLevel,您可以进行以下 JUnit 测试以确保 EmergencyPriority 具有所有组合:

    @Test
    public void testEnumCompletude() {
        for (BusinessCriticality c : BusinessCriticality.values()) {
            for (EmergencyLevel e : EmergencyLevel.values()) {
                assertNotNull(String.format("%s/%s combination was forgotten", c, e), 
                    EmergencyPriority.of(c, e));
            }
        }
    }
    

    【讨论】:

    • 这很有趣。样板在 switch...case 解决方案之上,但这看起来更具可读性并且更 OOP。我喜欢你如何在内部使用轻量级二维数组,而不依赖于枚举的序数值。还 +1 向我展示了有用的私有静态方法的第一个可行情况。唯一可能让读者感到困惑的是重载的构造函数:您将两对参数作为四个单独的参数传递。有一些格式(一对后的换行符?)这是我迄今为止最喜欢的。
    • @HubertGrzeskowiak 谢谢。我必须声明 2 个构造函数的事实也让我很恼火,但我没有看到更好的方法来使用不支持原生元组的 Java。我在第一对之后添加了一个换行符,我认为这样看起来还可以。
    • @Valentin java 没有元组类,因为制作它太容易了,你应该在 EmergencyPriority 枚举结构中创建一个私有的,而不是有很多构造函数,而是创建一个具有可变参数初始化的构造函数...类似于...private EmergencyPriority(Tuple... tuples){...},稍作更改,您的代码应该仍然可以工作...
    • @AlexandroSifuentesDíaz 当然,定义一个元组类(几乎在所有语言中)很容易,但必须调用它们的构造函数会在代码中添加很多字符(我试图使其变得简单)。如果元组是一种语言特性,我可以有一个构造函数,并且构造函数调用不会太冗长,比如private EmergencyPriority((BusinessCriticality, EmergencyLevel)... values) { values.forEach((c, l) -&gt; addPriority(c, l, this)); }
    • @HubertGrzeskowiak 我想出了另一个解决方案,它对我来说看起来更干净,源自我的第一个解决方案。
    【解决方案3】:

    也许是一个MapCriticalityLevel 的元组作为键?

    您可以使用自定义 equal()hashCode() 方法创建一个密钥类,该方法封装这两个值,如下所示:

    public class PriorityTuple{    
      final Criticality c;
      final Level l;
    
      public PriorityTuple(Criticality c, Level l) {
        this.c = c;
        this.l = l;
      }
    
      @Override
      public boolean equals(Object o) {
        if (!(o instanceof PriorityTuple)) {
          return false;
        }
        PriorityTuple prioritykey = (PriorityTuple) o;
        return this.c.equals(prioritykey.c) && this.l.equals(prioritykey.l);
      }
    
      @Override
      public int hashCode() {
        int hash = 5;
        hash = 23 * hash + Objects.hashCode(this.c);
        hash = 23 * hash + Objects.hashCode(this.l);
        return hash;
      }
    }
    

    然后使用您的实体创建您的Map

    Map<PriorityTuple, Priority> priorityMap = new HashMap<>();
    

    还有两种简化add和get的方法:

    // add new entry
    public static void addPriority(Criticality c, Level l, Priority p) {
      if (null == c || null == l || null == p) return; // if you want some kind of control
      priorityMap.put(new PriorityTuple(c, l), p);
    }
    // get priority
    public static Priority priority(Criticality c, Level l) {
      return priorityMap.get(new PriorityTuple(c, l));
    }
    

    结果如下:

    addPriority(Criticality.C1, Level.L1, Priority.P1);
    addPriority(Criticality.C1, Level.L2, Priority.P2);
    addPriority(Criticality.C1, Level.L3, Priority.P3);
    addPriority(Criticality.C2, Level.L1, Priority.P2);
    addPriority(Criticality.C2, Level.L2, Priority.P3);
    addPriority(Criticality.C2, Level.L3, Priority.P4);
    // and so on...
    
    // retrieving values...
    System.out.println(priority(Criticality.C1, Level.L1)); // print P1
    System.out.println(priority(Criticality.C4, Level.L3)); // null if not exist
    

    有了这个,您以后可以继续为您的枚举类型添加更多条目,而不会破坏代码 (?)

    【讨论】:

    • 我真的很喜欢如何将实际逻辑(最后一个代码块)从所有样板中分离出来。您可以轻松扩展它以捕获优先级(c,l)中的意外枚举值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多