Java中if语句的长列表
对不起,我找不到回答这个问题,我几乎可以肯定其他人之前提出过这个问题。
我的问题是,我正在编写一些系统库来运行embedded式设备。 我有可以通过无线电广播发送到这些设备的命令。 这只能通过文字完成。 在系统库里面我有一个线程来处理看起来像这样的命令
if (value.equals("A")) { doCommandA() } else if (value.equals("B")) { doCommandB() } else if etc.
问题是,有很多的命令会迅速旋转到失去控制。 可怕的看出来,痛苦的debugging,并在几个月的时间内令人难以理解。
使用命令模式 :
public interface Command { void exec(); } public class CommandA() implements Command { void exec() { // ... } } // etc etc
然后build立一个Map<String,Command>
对象并用Command
实例填充它:
commandMap.put("A", new CommandA()); commandMap.put("B", new CommandB());
那么你可以用ifreplaceif / else if链:
commandMap.get(value).exec();
编辑
您还可以添加特殊的命令,如UnknownCommand
或NullCommand
,但是您需要一个处理这些angular落情况的CommandMap
,以最大限度地减less客户端的检查。
那么有一个命令模式,但它可能是矫枉过正你正在做什么。 记住KISS。
我的build议是一种枚举和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或返回types。
如果doCommand的实现并不真正“适合”enumtypes,那么这种解决scheme可能并不适用,而像往常一样,您必须进行折衷 – 有点模糊。
通过dfa简单明了地实现一个界面,干净而优雅(和“正式”支持的方式)。 这是接口概念的意义。
在C#中,我们可以为喜欢在c中使用functon指针的程序员使用委托,但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; }
因此,构build你的命令,
public class CommandA extends Command { public CommandA(String subCmd) { this.cmdKey = "A"; this.subCmd = subCmd; } public byte exec() { sendWhatever(...); byte status = receiveWhatever(...); return status; } }
然后你可以通过提供一个键值对抽吸函数来扩展genericsHashMap或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); } } }
然后构build你的命令存储:
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();
有一个命令的枚举:
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; }
如果你有更多的命令,可以使用命令模式,就像在其他地方回答的一样(虽然你可以保留枚举并将调用embedded到枚举中的实现类中,而不是使用HashMap)。 例如,请参阅Andreas或jens对这个问题的回答。
那么我build议创build命令对象,并把它们放到一个哈希映射使用string作为关键。
即使我相信命令模式的方法更符合最佳实践,并且从长远来看仍然可以维持,下面给出一个一行的选项:
org.apache.commons.beanutils.MethodUtils.invokeMethod(这一点, “doCommand” +值,NULL);
我通常试图解决这个问题:
public enum Command { A {void exec() { doCommandA(); }}, B {void exec() { doCommandB(); }}; abstract void exec(); }
这有很多优点:
1)不可能在不执行exec的情况下添加一个枚举。 所以你不会错过一个A.
2)你甚至不必把它添加到任何命令地图,所以没有build立地图的样板代码。 只是抽象的方法和它的实现。 (这也可以说是样板,但不会变短)
3)通过查看if的长列表或计算hashCodes并进行查找,您将保存任何浪费的cpu周期。
编辑:如果你没有枚举,但string作为源,只需使用Command.valueOf(mystr).exec()
来调用exec方法。 请注意,您必须在execif上使用public修饰符,才能从另一个包中调用它。
您最好使用命令图。
但是,你是否有一套这样的方法来处理你,最终导致大量的地图被敲击。 那么值得用Enums来做。
你可以使用Enum而不使用开关来实现(如果你在Enum中添加一个方法来parsing“value”的话,你可能不需要这个例子中的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(); } }
在我看来,@dfa提供的答案是最好的解决scheme。
我只是提供一些片段,以防您使用Java 8并想使用Lambda!
不带参数的命令:
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
)作为参数。
如果你有多个重叠的“if”语句,那么这是一个使用规则引擎的模式。 参见例如JBOSS Drools 。
只需使用一个HashMap,如下所述:
如果有可能有一个程序(你所谓的命令),这将是有用的数组。
但你可以编写一个程序来编写你的代码。 如果(值='A')commandA(); 否则如果(……………………等
我不确定在各种命令的行为之间是否有任何重叠,但是您可能还想看看可以通过允许多个命令处理一些input值来提供更多灵活性的责任链模式。
如果它做了很多事情,那么会有很多代码,你真的不能逃避。 只要简单易行,给variables赋予非常有意义的名字,评论也可以帮助…