【问题标题】:What is the best way to set class variable which is an enum based on some conditional logic on a dependent enum in java?设置类变量的最佳方法是什么,该类变量是基于java中依赖枚举的一些条件逻辑的枚举?
【发布时间】:2018-10-10 00:53:07
【问题描述】:

我正在尝试根据我需要执行的一些计算或条件逻辑将值从一个枚举映射到另一个枚举,以便为类变量设置正确的枚举值。如何在不使用太多 if/else、switch 语句的情况下做到这一点?

Enum BRAND {
 MINI, FERRARI, PAGANI
}

还有另一个枚举

Enum ENGINE {
LEVEL1, LEVEL2, LEVEL3
}

我有一个像这样的课程:

Class Car() {

 int model;
 int year;
 Engine engine;

 // I want to calculate set the engine depending on the brand based on conditional logic
 public carEngineCalculator (Brand b) {
   Car mycar = new Car();

   if (mycar.isSuperCar(b) {
    if (mycar.isCrazyGood(b)) {
        mycar.engine = ENGINE.LEVEL1;
    } else {
        mycar.engine = ENGINE.LEVEL2;
    }
   } else {
    mycar.engine = ENGINE.LEVEL3;
   }
   ... //And the conditions can be more complex
 }

 public boolean isSuperCar(Brand b) {
    if (b.FERRARI || b.PAGANI) {
     return true;
    } 
    return false;
 }

 public boolean isCrazyGood(Brand b) {
    return ...;
 }
} 

为了设置值,可能需要检查多个这样的条件,并且我想避免讨厌的 if/else/switch 语句,如上所示。有没有更实用的方法来做到这一点。

【问题讨论】:

  • 您在这里违反了命名约定。类名(枚举就是类!)应该总是以驼峰命名:“Brand”和“Engine”。另外我认为“引擎”不是一个好名字,因为枚举实际上不是描述整个引擎,而是引擎的某种分类。为什么不叫它“EngineLevel”呢? "enuM" 和 "class" 也必须是小写,但我猜这是拼写错误。
  • @Amadán 你是对的。
  • 该示例过于笼统,无法给出好的答案,但看起来 BRAND 枚举和 ENGINE 枚举之间存在一对一的映射。如果是这种情况,那么只需让 BRAND 枚举返回其特定的 ENGINE 枚举。
  • 您可以创建一个 Map ,其中 Predicate 为 Key,结果 enum-value 作为 value(或 Pairs Predicate/enum-value 列表)。您可以使用 Java 8-streams 来检查这些谓词中的哪一个是正确的并获得所需的枚举值。
  • 很好@RalfRenz 我喜欢这个主意。你能举个例子吗?

标签: java enums java-8


【解决方案1】:

使用谓词就像我说的那样:

public enum Brand {
    MINI,
    FERRARI,
    PAGANI
}

public enum Engine {
    LEVEL1,
    LEVEL2,
    LEVEL3
}

public class Entry {
    public final Predicate<Car> pred;
    public final Engine engine;

    public Entry(Predicate<Car> pred, Engine engine) {
        this.pred = pred;
        this.engine = engine;
    }
}

public class Car {
    int model;
    int year;
    Engine engine;

    public void carEngineCalculator(Brand b) {
        Car mycar = new Car();

        List<Entry> cases = new ArrayList<>();
        cases.add(new Entry(c -> c.isSuperCar(b) && c.isCrazyGood(b), Engine.LEVEL1));
        cases.add(new Entry(c -> c.isSuperCar(b) && !c.isCrazyGood(b), Engine.LEVEL2));
        cases.add(new Entry(c -> !c.isSuperCar(b), Engine.LEVEL3));

        mycar.engine = cases.stream().filter(x -> x.pred.test(mycar)).findFirst().get().engine;

    }

    public boolean isSuperCar(Brand b) {
        if ((b == Brand.FERRARI) || (b == Brand.PAGANI)) {
            return true;
        }
        return false;
    }

    public boolean isCrazyGood(Brand b) {
        return false;
    }
}

您创建一个包含谓词和结果的列表,并使用流、过滤器和 findFirst 来遍历列表并找到正确的结果。如果条件比你不需要谓词更简单,测试它有点不同。

【讨论】:

    【解决方案2】:

    如果品牌和引擎的映射是一对一的,您可以这样做:

    enum Brand {
        MINI(Engine.LEVEL1),
        FERRARI(Engine.LEVEL2),
        PAGANI(Engine.LEVEL3);
    
        private final Engine engine;
    
        private Brand(Engine engine) {
            this.engine = engine;
        }
    
        public final Engine getEngine() {
            return engine;
        }
    }
    

    另一种选择:

    enum Brand {
        MINI(false, false),
        FERRARI(true, true),
        PAGANI(false, true);
    
        private final boolean superCar;
        private final boolean crazyGood;
    
        private Brand(boolean superCar, boolean crazyGood) {
            this.superCar = superCar;
            this.crazyGood = crazyGood;
        }
    
        public final Engine getEngine() {
            if (superCar) {
                return (crazyGood) ? Engine.LEVEL1 : Engine.LEVEL2;
            }
            return Engine.LEVEL3;
        }
    }
    

    如果映射不是一对一的,并且您需要根据某些参数以某种方式动态计算引擎,您也可以使用这个:

    enum Brand {
        MINI {
            @Override
            public Engine getEngine(boolean superCar, boolean crazyGood) {
                return (superCar && crazyGood) ? Engine.LEVEL1 : Engine.LEVEL2;
            }
        },
        FERRARI {
            @Override
            public Engine getEngine(boolean superCar, boolean crazyGood) {
                return superCar ? Engine.LEVEL1 : Engine.LEVEL3;
            }
        },
        PAGANI {
            @Override
            public Engine getEngine(boolean superCar, boolean crazyGood) {
                return Engine.LEVEL3;
            }
        };
    
        public abstract Engine getEngine(boolean superCar, boolean crazyGood);
    }
    

    或者类似的东西,你有一些默认值并仅针对特殊情况进行覆盖:

    enum Brand {
        MINI,
        FERRARI {
            @Override
            public Engine getEngine(boolean superCar, boolean crazyGood) {
                return superCar ? Engine.LEVEL1 : Engine.LEVEL3;
            }
        },
        PAGANI;
    
        public Engine getEngine(boolean superCar, boolean crazyGood) {
            return Engine.LEVEL3;
        }
    }
    

    仅使用枚举有很多可能性,我实际上更喜欢复杂的 if/else 或 switch 语句。当然,这取决于您到底想做什么,并且由于提供的信息不多,因此我无法真正给出最佳答案。希望对您有所帮助。

    【讨论】:

      【解决方案3】:

      首先,将isSuperCarisCrazyGood 方法移到Brand,而不是让它们采用Brand 参数。您可以类似地向Engine 添加一个静态工厂方法,该方法封装了您尝试编码的逻辑。这并不能完全避免“讨厌的 if/else/switch 语句”,但它可能更具可读性。

      例如:

      public Car(Brand b) {
        this.engine = Engine.forBrand(b);
      }
      

      然后:

      enum Engine {
        LEVEL1, LEVEL2, LEVEL3
      
        public static Engine forBrand(Brand b) {
          if (b.isSuperCar()) {
            return b.isCrazyGood() ? LEVEL1 : LEVEL2;
          }
          return LEVEL3;
        }
      }
      

      另请注意,您的 isSuperCar 方法可以是:

      return b.equals(Brand.FERRARI) || b.equals(Brand.PAGANI);
      

      永远不需要写 if (...) return true; else return false; 或类似的东西 - 只需直接在 if 语句中使用布尔表达式即可。

      【讨论】:

      • 是的,if/else 只是为了演示一个简单的例子,条件可能不是那么简单,即可能有多个检查 ANDed 或 ORed 一起。我的问题是,如果引擎 ENUM 是自动生成的或者是另一个库的一部分,那么不能真正直接修改。我应该创建一个包装器枚举还是一个辅助类来执行类似于您建议的操作?
      • 好吧,如果您的实际限制不是您在问题中提出的,那么很难给您一个好的答案。如果不能将方法放在枚举上,则必须改用静态实用程序方法(考虑使用 Engines 类来保存此类方法,Brands 等也是如此)。如果您使用正在处理的 AND 和 OR 的更具体示例更新您的问题,我可能会提供更有用的建议。
      • 你是对的,没有理由写if (...) return true; else return false;,但return b.FERRARI || b.PAGANI; 与原始语句一样无效,因为b.FERRARI || b.PAGANI 不是有效的测试条件。另一方面,该问题包含的语法错误多于正确的代码。除此之外,我不明白为什么要将一个 if 语句转换为条件表达式而不是另一个,即只使用 return b.isSuperCar()? b.isCrazyGood()? LEVEL1: LEVEL2: LEVEL3;
      • 好点,大概应该是b.equals(...)。至于嵌套三元,只是个人风格。您当然可以嵌套它们,但我认为它不会提高代码的清晰度。
      • 对于enum 常量,也可以使用==。关于嵌套的三元表达式,它们通过调整顺序变得更具可读性,即!b.isSuperCar()? LEVEL3: b.isCrazyGood()? LEVEL1: LEVEL2,其中条件与其结果相邻......
      【解决方案4】:

      我可能会将品牌存储在 Car 类中,但这是另一个问题。我会在Car 类中有一个静态地图,用于跟踪每个品牌的引擎(如Ralf Renz 建议的那样):

      Class Car {
      
       int model;
       int year;
       Engine engine;
       static Map<Brand, Engine> carEngineMap = new HashMap<>();
      
       public static void setBrandEngine(Brand b, Engine e) {
         carEngineMap.put(b, e);
       }
      
       // I want to calculate set the engine depending on the brand based on conditional logic
       public carEngineCalculator (Brand b) {
         Car mycar = new Car();
      
         mycar.engine = carEngineMap.get(b);
       }
      
       public boolean isSuperCar(Brand b) {
          if (b.FERRARI || b.PAGANI) {
           return true;
          } 
          return false;
       }
      
       public boolean isCrazyGood(Brand b) {
          return ...;
       }
      }
      

      然后,您可以为每个品牌分别输入条件,例如

      Car.setBrandEngine(Brand.FERRARI, Engine.LEVEL2);
      Car.setBrandEngine(Brand.PAGANI, Engine.LEVEL1);
      ...
      

      【讨论】:

        【解决方案5】:

        使用谓词 CONDITION(brand, engine, condition) 创建枚举并在那里提供您的条件。之后:

        Conditions.values().foreach(condition -> {
            condition.isApplicable(variblesDto) ? return condition.apply() : continue;
        });
        

        并且您可以在需要新条件的任何时候更新您的枚举。它也将按 Enum 的顺序排列,因此您可以使用它

        【讨论】:

          猜你喜欢
          • 2016-05-19
          • 1970-01-01
          • 1970-01-01
          • 2022-12-25
          • 2015-08-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-08-26
          相关资源
          最近更新 更多