【问题标题】:Best way execute code according enum value根据枚举值执行代码的最佳方式
【发布时间】:2021-07-05 13:29:19
【问题描述】:

以下代码是我的问题的一个示例。 我想简化代码,而不必在不同的 switch 语句上重复调用相同的方法。

public void simulate(String given, Status status) {
    switch (status){
        case A:
            simulateA(given);
            break;
        case B:
            simulateA(given);
            simulateB(given);
            break;
        case C:
            simulateA(given);
            simulateB(given);
            simulateC(given);
            break;
    }

PS 1:调用方法的顺序很重要!

PS 2:我不是在寻找另一种进行切换的方式,我在寻找另一种对问题进行建模的方式,也许是在方法中使用某种类组合。

【问题讨论】:

    标签: java coding-style


    【解决方案1】:

    我不知道你的enum 的性质,但如果你有很多模拟调用,你可以放弃 switch 语句并像这样做。但是您目前的方法没有任何问题。如果您的方法是静态的而不是实例,这也会稍微改变。这样做的一个优点是它具有扩展的潜力。

    还有很多其他方法可以做到这一点。您可以有一个方法引用列表,enum 参数可以是按索引调用哪些方法的变量数组。

    public class Simulations {
        
        static List<BiConsumer<Simulations, String>> sims =
                List.of(Simulations::simulateA, Simulations::simulateB,
                        Simulations::simulateC);
        
        enum Status {
            A(1), B(2), C(3);
            private int val;
            private Status(int v) {
                this.val = v;
            }
            public int getVal() {
                return val;
            }
        }
        
        public static void main(String[] args) {
            Simulations simulation = new Simulations();
            simulation.simulate("A", Status.A);
            System.out.println();
            simulation.simulate("B", Status.B);
            System.out.println();
            simulation.simulate("C", Status.C);
        }
        
        public void simulate(String given, Status status) {
            for (int i = 0; i < status.getVal(); i++) {
                sims.get(i).accept(this, given);
            }
        }
        
        public void simulateA(String s) {
            System.out.println(s);
        }
        
        public void simulateB(String s) {
            System.out.println(s);
        }
        
        public void simulateC(String s) {
            System.out.println(s);
        }
        
    }
    

    【讨论】:

      【解决方案2】:

      在这种情况下,模拟的顺序总是“向下”级联,例如模拟 B 是模拟 A 加上一些额外的。这匹配一个继承模式,例如哺乳动物是一种具有一些附加功能的动物。因此,让模拟相互继承修复了这种模式:

      interface Simulation
      {
          void simulate( final String given );
      }
      
      class ASimulation implements Simulation
      {
          @Override
          public void simulate( String given )
          {
              // simulate this given!
          }
      }
      
      class BSimulation extends ASimulation
      {
          @Override
          public void simulate( String given )
          {
              super.simulate( given );
              // simulate this given some more!
          }
      }
      
      class CSimulation extends BSimulation
      {
          @Override
          public void simulate( String given )
          {
              super.simulate( given );
              // simulate this given even more!
          }
      }
      

      请注意,这很脆弱,就像所有继承树一样。另一种解决方案可以通过组合和委托来实现。这称为链:

      class LeafSimulation
              implements Simulation
      {
          @Override
          public void simulate( String given )
          {
              // simulate this given!
          }
      }
      
      class ChainedSimulation
              implements Simulation
      {
          private final Simulation delegate;
      
          ChainedSimulation( final Simulation delegate )
          {
              this.delegate = delegate;
          }
      
          @Override
          public void simulate( String given )
          {
              delegate.simulate( given );
              // simulate this given some more!
          }
      }
      

      要实例化链,请使用以下顺序:

      final var aSimulation = new LeafSimulation();
      final var bSimulation = new ChainedSimulation( aSimulation );
      final var cSimulation = new ChainedSimulation( bSimulation );
      

      这段代码更自然地接近问题陈述,消除了重复,但并不简洁。

      【讨论】:

        【解决方案3】:

        设置状态值到方法调用的映射后,您可以使用SortedSetEnumSet.range 来获取特定值之后的枚举值:

        Map<Status, Consumer<String>> simulators = new EnumMap<>(Map.of(
            Status.A, this::simulateA,
            Status.B, this::simulateB,
            Status.C, this::simulateC));
        
        if (!simulators.keySet().equals(EnumSet.allOf(Status.class))) {
            throw new RuntimeException(
                "Not all Status values have simulators defined.");
        }
        
        // ...
        
        SortedSet<Status> all = new TreeSet<>(EnumSet.allOf(Status.class));
        Collection<Status> remainingValues = all.tailSet(status);
        
        // Or:
        //Status[] allStatuses = Status.values();
        //Status lastStatus = allStatuses[allStatuses.length - 1];
        //Collection<Status> remainingValues = EnumSet.range(status, lastStatus);
        
        for (Status s : remainingValues) {
            simulators.get(s).accept(given);
        }
        

        【讨论】:

          【解决方案4】:

          另一个需要考虑的选项,它避免了switch / if。为每个 Status 值声明一个动作映射,该映射可与 getOrDefault 查找默认值一起使用:

          Consumer<String> simA = this::simulateA;
          Map<Status, Consumer<String>> actions = new EnumMap<>(Map.of(
                  Status.A, simA,
                  Status.B, simA.andThen(this::simulateB),
                  Status.C, simA.andThen(this::simulateB).andThen(this::simulateC)
          ));
          actions.getOrDefault(status, s -> {}).accept(given);
          

          如果您想防止丢失/未处理的映射,您应该验证映射(如@VGR 答案)或用异常处理程序交换无操作默认值:

          actions.getOrDefault(status,
              s -> { throw new RuntimeException("Missing action for status: "+status); }
          ).accept(given);
          

          【讨论】:

            【解决方案5】:

            假设你的第一个 status 是 A,你可以这样做:

            public void simulate(String given, Status status) {
               if (status != Status.A) {
                 int indexOfStatus = status.ordinal();
                 simulate(given, Status.values()[indexOfStatus - 1]);
               }
                
               switch (status){
                  case A:
                    simulateA(given);
                    break;
                  case B:
                    simulateB(given);
                    break;
                  case C:
                    simulateC(given);
                    break;
                // here you still need to put all your "simulateX" calls but without repetitions
               }
            }
            

            【讨论】:

            • 这会以相反的顺序执行方法。
            • @VGR 我在回答之前运行代码,它以正确的顺序执行
            • 啊,是的,你是对的。对您的答案进行少量编辑(即使只是一些额外的空间),以便我可以撤消我的反对意见。对此感到抱歉。
            【解决方案6】:

            您不需要在所有情况下都写simulateA(given),只需将其移至顶部

            public void simulate(String given, Status status) {
               simulateA(given);
               switch (status){
                 case C:
                   simulateC(given);
                 case B:
                   simulateB(given);
                   break;
                 case A:
                   break;
             }}
            

            【讨论】:

            • 这不起作用,因为您以不同的顺序调用它们。例如A,C,B当状态C
            【解决方案7】:

            你可以试试switch语句的fallthrough机制。参考this

            在您的示例中,代码可以(未测试):

            编辑:

            public void simulate(String given, Status status) {
                switch (status){
                    case C:
                        simulateC(given);
                    case B:
                        simulateB(given);
                    case A:
                        simulateA(given);
                }
            }
            

            原文(错误):

            public void simulate(String given, Status status) {
                switch (status){
                    case A:
                        simulateA(given);
                    case B:
                        simulateB(given);
                    case C:
                        simulateC(given);
                }
            }
            

            当读者心中有fallthrough 的概念时,上面的代码比有问题的代码更清晰、更容易阅读。但情况并非总是如此。我的建议是重构您的代码,以消除重复调用和失败。

            【讨论】:

            • 如果状态为 A 则所有方法都运行?
            • 这与想要的行为相反,他想要 A 只 A、B AB 和 C ABC。使用您的代码将获得:A -> ABC, B -> BC, C -> C.
            猜你喜欢
            • 2017-08-01
            • 1970-01-01
            • 2019-04-03
            • 2012-10-21
            • 2010-12-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多