102

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

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

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

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

4

15 回答 15

172

使用命令模式

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

编辑

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

于 2009-07-29T11:45:23.597 回答
12

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

于 2009-07-29T12:09:49.590 回答
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;
}

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

于 2009-07-29T11:47:26.367 回答
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();
于 2009-07-30T02:42:23.080 回答
5

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

于 2009-07-29T11:46:43.280 回答
3

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

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

于 2009-07-29T12:00:50.423 回答
2

我通常尝试以这种方式解决它:

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 方法。请注意,如果要从另一个包中调用它,则必须在 execif 上使用 public 修饰符。

于 2009-07-29T12:02:16.643 回答
2

您可能最好使用命令图。

但是,您是否有一组这些来处理您最终会遇到大量地图敲门。然后值得考虑用 Enums 来做这件事。

如果您向 Enum 添加方法以解析“值”,则可以使用 Enum 而不使用开关(示例中可能不需要 getter)。然后你可以这样做:

更新:添加静态地图以避免每次调用的迭代。无耻地从这个答案中捏了出来。

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();
    }
}
于 2009-07-29T12:26:05.123 回答
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);

在上面的示例doSomethingX中, 的类中存在一个方法,该方法myObj将任何 Object(param在此示例中命名)作为参数。

于 2014-07-02T22:17:25.463 回答
1

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

于 2009-07-29T12:01:59.937 回答
0

只需使用 HashMap,如下所述:

于 2009-07-29T11:47:06.260 回答
0

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

但你可以编写一个程序来编写你的代码。这一切都非常系统 if(value='A') commandA(); 否则如果(..............等

于 2009-07-29T12:00:29.567 回答
0

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

于 2009-07-29T13:30:37.787 回答
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"
  }
于 2019-03-11T15:27:16.517 回答
-1

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

于 2009-07-29T11:45:52.363 回答