【问题标题】:Long list of if statements in JavaJava中if语句的长列表
【发布时间】:2010-11-15 00:56:42
【问题描述】:

抱歉,我找不到回答这个问题的问题,我几乎可以肯定其他人之前已经提出过这个问题。

我的问题是我正在编写一些系统库来运行嵌入式设备。我有可以通过无线电广播发送到这些设备的命令。这只能通过文本来完成。在系统库中我有一个线程来处理看起来像这样的命令

if (value.equals("A")) { doCommandA() }
else if (value.equals("B")) { doCommandB() } 
else if etc. 

问题是它有很多命令会很快变得失控。看起来很糟糕,调试起来很痛苦,几个月后理解起来令人难以置信。

【问题讨论】:

  • 只是一个评论——我强烈建议您阅读 Gang of Four patterns 书,或者如果您是模式新手,请阅读 Head First Design Patterns in Java 书(这本书很容易阅读,很好地介绍了一些常见的模式)。两者都是宝贵的资源,都不止一次救了我的培根。
  • 是的,实际上我拥有它们,但它们不见了 :) 这就是为什么我确定自己做错了 :) 虽然找不到正确的解决方案!也许这会得到一个不错的谷歌位置
  • 这里只是周一的命令模式!

标签: java design-patterns command-pattern


【解决方案1】:

使用Command pattern:

public interface Command {
     void exec();
}

public class CommandA() implements Command {

     void exec() {
          // ... 
     }
}

// etc etc

然后构建一个Map<String,Command> 对象并用Command 实例填充它:

commandMap.put("A", new CommandA());
commandMap.put("B", new CommandB());

那么您可以将 if/else if 链替换为:

commandMap.get(value).exec();

编辑

您还可以添加特殊命令,例如UnknownCommandNullCommand,但您需要一个处理这些极端情况的CommandMap,以最大限度地减少客户的检查。

【讨论】:

  • ...适当检查 commandMap.get() 不返回 null :-)
  • 当然,为了简单起见,我省略了一些样板代码
  • 您可以使用 Java 枚举代替 HashMap,它为您提供了一组明确定义的命令,而不是糊状的映射。你可以在枚举中有一个吸气剂:Command getCommand();甚至将 exec() 实现为枚举中的抽象方法,每个实例都实现该方法(枚举作为命令)。
  • 这将强制实现枚举中的所有命令......这远非理想。使用接口,您还可以应用装饰器模式(例如 DebugCommandDecorator、TraceCommandDecorator),在简单的 Java 接口中内置了更多的灵活性
  • 好的,对于小型且永不增长的命令集,枚举是一个可行的解决方案。
【解决方案2】:

我的建议是枚举和 Command 对象的一种轻量级组合。这是 Joshua Bloch 在 Effective Java 第 30 条中推荐的成语。

public enum Command{
  A{public void doCommand(){
      // Implementation for A
    }
  },
  B{public void doCommand(){
      // Implementation for B
    }
  },
  C{public void doCommand(){
      // Implementation for C
    }
  };
  public abstract void doCommand();
}

当然,您可以将参数传递给 doCommand 或具有返回类型。

如果 doCommand 的实现并不真正“适合”枚举类型,那么这个解决方案可能并不适合,这 - 像往常一样,当你必须做出权衡时 - 有点模糊。

【讨论】:

    【解决方案3】:

    有一个命令的枚举:

    public enum Commands { A, B, C; }
    ...
    
    Command command = Commands.valueOf(value);
    
    switch (command) {
        case A: doCommandA(); break;
        case B: doCommandB(); break;
        case C: doCommandC(); break;
    }
    

    如果您有多个命令,请考虑使用命令模式,正如其他地方所回答的那样(尽管您可以保留枚举并将对实现类的调用嵌入枚举中,而不是使用 HashMap)。请参阅 Andreas 或 jens 对此问题的回答作为示例。

    【讨论】:

    • 每增加一个新命令,都需要编辑开关:这段代码不遵循开闭原则
    • 取决于命令是少还是多,不是吗?此外,这个网站现在速度非常慢,需要 5 次尝试才能编辑答案。
    • 这不是最佳的,请参阅stackoverflow.com/questions/1199646/…,了解如何更优化。
    • 是的,感谢您花时间实现我在评论底部所写的内容 - Java Enum as Command Pattern。如果我可以编辑我的帖子,我会提到这一点,但这个网站正在死去。
    • 我认为这个问题是在为 Switch 声明尖叫!
    【解决方案4】:

    实现一个由 dfa 简单明了地演示的接口是干净而优雅的(并且是“官方”支持的方式)。这就是接口概念的意义所在。

    在 C# 中,我们可以为喜欢在 c 中使用函数指针的程序员使用委托,但 DFA 的技术是使用方式。

    你也可以有一个数组

    Command[] commands =
    {
      new CommandA(), new CommandB(), new CommandC(), ...
    }
    

    然后你可以通过索引执行命令

    commands[7].exec();
    

    抄袭 DFA,但有一个抽象基类而不是接口。请注意稍后将使用的 cmdKey。根据经验,我意识到设备命令通常也有子命令。

    abstract public class Command()
    {
      abstract public byte exec(String subCmd);
      public String cmdKey;
      public String subCmd;
    }
    

    这样构造你的命令,

    public class CommandA
    extends Command
    {
      public CommandA(String subCmd)
      {
        this.cmdKey = "A";
        this.subCmd = subCmd;
      }
    
      public byte exec()
      {
        sendWhatever(...);
        byte status = receiveWhatever(...);
        return status;
      }
    }
    

    然后您可以通过提供键值对吸吮函数来扩展通用 HashMap 或 HashTable:

    public class CommandHash<String, Command>
    extends HashMap<String, Command>
    (
      public CommandHash<String, Command>(Command[] commands)
      {
        this.commandSucker(Command[] commands);
      }
      public commandSucker(Command[] commands)
      {
        for(Command cmd : commands)
        {
          this.put(cmd.cmdKey, cmd);
        }
      }
    }
    

    然后构建你的命令存储:

    CommandHash commands =
      new CommandHash(
      {
        new CommandA("asdf"),
        new CommandA("qwerty"),
        new CommandB(null),
        new CommandC("hello dolly"),
        ...
      });
    

    现在您可以客观地发送控制

    commands.get("A").exec();
    commands.get(condition).exec();
    

    【讨论】:

    • +1 用于提及委托,以防万一任何 .NET 人员看到这个问题并为单一方法接口而疯狂。但它们确实无法与函数指针相提并论。它们更接近命令模式的语言支持版本。
    【解决方案5】:

    好吧,我建议创建命令对象,并使用字符串作为键将它们放入哈希图中。

    【讨论】:

      【解决方案6】:

      即使我认为命令模式方法更倾向于最佳实践并且从长远来看是可维护的,这里有一个适合您的选项:

      org.apache.commons.beanutils.MethodUtils.invokeMethod(this,"doCommand"+value,null);

      【讨论】:

        【解决方案7】:

        我通常会尝试这样解决:

        public enum Command {
        
        A {void exec() {
             doCommandA();
        }},
        
        B {void exec() {
            doCommandB();
        }};
        
        abstract void exec();
         }
        

        这有很多优点:

        1) 如果不实现 exec,就无法添加枚举。所以你不会错过一个A。

        2) 您甚至不必将其添加到任何命令映射中,因此没有用于构建映射的样板代码。只是抽象方法及其实现。 (这也可以说是样板,但它不会变得更短..)

        3) 您将通过检查一长串 if 或计算 hashCode 并进行查找来节省任何浪费的 cpu 周期。

        编辑: 如果你没有枚举但字符串作为源,只需使用Command.valueOf(mystr).exec() 调用 exec 方法。 请注意,如果您想从另一个包中调用它,则必须在 exec 上使用 public 修饰符。

        【讨论】:

          【解决方案8】:

          您可能最好使用命令图。

          但是您是否有一组这些来处理您最终会遇到大量地图敲打。那么值得考虑用 Enums 来做。

          如果您向 Enum 添加一个方法来解析“值”,您可以在不使用开关的情况下使用 Enum(您可能不需要示例中的 getter)。然后你可以这样做:

          更新:添加静态地图以避免每次调用的迭代。被this answer无耻捏了一把。

          Commands.getCommand(value).exec();
          
          public interface Command {
              void exec();
          }
          
          public enum Commands {
              A("foo", new Command(){public void exec(){
                  System.out.println(A.getValue());
              }}),
              B("bar", new Command(){public void exec(){
                  System.out.println(B.getValue());
              }}),
              C("barry", new Command(){public void exec(){
                  System.out.println(C.getValue());
              }});
          
              private String value;
              private Command command;
              private static Map<String, Commands> commandsMap;
          
              static {
                  commandsMap = new HashMap<String, Commands>();
                  for (Commands c : Commands.values()) {
                      commandsMap.put(c.getValue(), c);    
                  }
              }
          
              Commands(String value, Command command) {
                  this.value= value;
                  this.command = command;
              }
          
              public String getValue() {
                  return value;
              }
          
              public Command getCommand() {
                  return command;
              }
          
              public static Command getCommand(String value) {
                  if(!commandsMap.containsKey(value)) {
                      throw new RuntimeException("value not found:" + value);
                  }
                  return commandsMap.get(value).getCommand();
              }
          }
          

          【讨论】:

            【解决方案9】:

            在我看来,@dfa 提供的答案是最好的解决方案。

            我只是提供一些 sn-ps 以防您使用 Java 8 并想使用 Lambdas!

            不带参数的命令:

            Map<String, Command> commands = new HashMap<String, Command>();
            commands.put("A", () -> System.out.println("COMMAND A"));
            commands.put("B", () -> System.out.println("COMMAND B"));
            commands.put("C", () -> System.out.println("COMMAND C"));
            commands.get(value).exec();
            

            (您可以使用 Runnable 代替 Command,但我认为它在语义上不正确):

            一个参数的命令:

            如果您期望一个参数,您可以使用java.util.function.Consumer

            Map<String, Consumer<Object>> commands = new HashMap<String, Consumer<Object>>();
            commands.put("A", myObj::doSomethingA);
            commands.put("B", myObj::doSomethingB);
            commands.put("C", myObj::doSomethingC);
            commands.get(value).accept(param);
            

            在上面的例子中,doSomethingXmyObj 的类中的一个方法,它接受任何 Object(在本例中名为 param)作为参数。

            【讨论】:

              【解决方案10】:

              如果您有多个重叠的“if”语句,那么这是使用规则引擎的模式。例如,请参阅JBOSS Drools

              【讨论】:

                【解决方案11】:

                只需使用 HashMap,如下所述:

                【讨论】:

                  【解决方案12】:

                  如果有可能有一个有用的过程数组(你称之为命令)..

                  但是您可以编写一个程序来编写您的代码。这一切都非常系统 如果(值='A')命令A();别的 如果(........................ 等等

                  【讨论】:

                    【解决方案13】:

                    我不确定您的各种命令的行为之间是否有任何重叠,但您可能还想看看Chain Of Responsibility 模式,它可以通过允许多个命令处理一些输入值来提供更大的灵活性.

                    【讨论】:

                      【解决方案14】:

                      命令模式是要走的路。这是一个使用 java 8 的示例:

                      1.定义接口:

                      public interface ExtensionHandler {
                        boolean isMatched(String fileName);
                        String handle(String fileName);
                      }
                      

                      2。使用每个扩展实现接口:

                      public class PdfHandler implements ExtensionHandler {
                        @Override
                        public boolean isMatched(String fileName) {
                          return fileName.endsWith(".pdf");
                        }
                      
                        @Override
                        public String handle(String fileName) {
                          return "application/pdf";
                        }
                      }
                      

                      public class TxtHandler implements ExtensionHandler {
                        @Override public boolean isMatched(String fileName) {
                          return fileName.endsWith(".txt");
                        }
                      
                        @Override public String handle(String fileName) {
                          return "txt/plain";
                        }
                      }
                      

                      等等.....

                      3.定义客户端:

                      public class MimeTypeGetter {
                        private List<ExtensionHandler> extensionHandlers;
                        private ExtensionHandler plainTextHandler;
                      
                        public MimeTypeGetter() {
                          extensionHandlers = new ArrayList<>();
                      
                          extensionHandlers.add(new PdfHandler());
                          extensionHandlers.add(new DocHandler());
                          extensionHandlers.add(new XlsHandler());
                      
                          // and so on
                      
                          plainTextHandler = new PlainTextHandler();
                          extensionHandlers.add(plainTextHandler);
                        }
                      
                        public String getMimeType(String fileExtension) {
                          return extensionHandlers.stream()
                            .filter(handler -> handler.isMatched(fileExtension))
                            .findFirst()
                            .orElse(plainTextHandler)
                            .handle(fileExtension);
                        }
                      }
                      

                      4.这是示例结果:

                        public static void main(String[] args) {
                          MimeTypeGetter mimeTypeGetter = new MimeTypeGetter();
                      
                          System.out.println(mimeTypeGetter.getMimeType("test.pdf")); // application/pdf
                          System.out.println(mimeTypeGetter.getMimeType("hello.txt")); // txt/plain
                          System.out.println(mimeTypeGetter.getMimeType("my presentation.ppt")); // "application/vnd.ms-powerpoint"
                        }
                      

                      【讨论】:

                        【解决方案15】:

                        如果它做了很多事情,那么就会有很多代码,你真的无法摆脱这一点。让它易于理解,给变量起非常有意义的名字,cmets 也可以提供帮助......

                        【讨论】:

                          猜你喜欢
                          • 2016-03-04
                          • 1970-01-01
                          • 2020-07-30
                          • 1970-01-01
                          • 2014-11-28
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2014-10-29
                          相关资源
                          最近更新 更多