Java中if语句的长列表

时间:2009-07-29 11:40:48

标签: java design-patterns command-pattern

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

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

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

问题在于它有很多命令会迅速失控。看起来很可怕,调试很痛苦,并且在几个月的时间里难以理解。

15 个答案:

答案 0 :(得分:167)

使用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来处理这些极端情况,以便最大限度地减少客户的检查。

答案 1 :(得分:12)

我的建议是enum和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的实现并不真正“适合”枚举类型,那么这个解决方案可能并不合适,这就像往常一样,当你必须做出权衡时 - 有点模糊。

答案 2 :(得分:7)

有一个命令枚举:

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;
}

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

答案 3 :(得分:7)

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();

答案 4 :(得分:5)

我建议创建命令对象并使用String作为键将它们放入哈希映射中。

答案 5 :(得分:3)

即使我相信命令模式方法更倾向于最佳实践并且长期可维护,这里有一个单行选项:

org.apache.commons.beanutils.MethodUtils.invokeMethod(这一点, “doCommand” +值,NULL);

答案 6 :(得分:2)

我通常会尝试这样解决:

public enum Command {

A {void exec() {
     doCommandA();
}},

B {void exec() {
    doCommandB();
}};

abstract void exec();
 }
这有很多好处:

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

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

3)你将通过查看if的长列表或计算hashCodes并进行查找来节省任何浪费的cpu周期。

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

答案 7 :(得分:2)

你可能最好使用命令图。

但是你有一套这样的东西来处理你最终的大量地图敲门。那么值得看看用Enums做这件事。

如果向Enum添加方法以解析“value”,则可以使用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();
    }
}

答案 8 :(得分:2)

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

我只是提供了一些代码片段以防你使用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)作为参数。

答案 9 :(得分:1)

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

答案 10 :(得分:0)

只需使用HashMap,如下所述:

答案 11 :(得分:0)

如果有可能有一系列程序(你称之为命令)那么有用..

但您可以编写一个程序来编写代码。这一切都非常系统化 if(value ='A')commandA();其他 如果(........................ e.t.c。

答案 12 :(得分:0)

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

答案 13 :(得分:0)

要使用命令模式。这是一个使用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"
  }

答案 14 :(得分:-1)

如果它做了很多事情,那么会有很多代码,你真的无法摆脱它。只是让它易于理解,给变量非常有意义的名称,评论也可以帮助......