Larga lista de sentencias if en Java

Lo siento, no puedo encontrar una pregunta que responda esto, estoy casi seguro de que alguien más lo ha planteado antes.

Mi problema es que estoy escribiendo algunas bibliotecas del sistema para ejecutar dispositivos integrados. Tengo comandos que pueden enviarse a estos dispositivos a través de transmisiones de radio. Esto solo puede hacerse por texto. dentro de las bibliotecas del sistema tengo un hilo que maneja los comandos que se parece a esto

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

El problema es que hay muchos comandos que se convertirán rápidamente en algo fuera de control. Horrible de mirar, doloroso de depurar y mente alucinante para entender en unos meses.

usando el patrón de comando :

 public interface Command { void exec(); } public class CommandA() implements Command { void exec() { // ... } } // etc etc 

luego construya un objeto Map y rellene con instancias Command :

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

entonces puedes reemplazar tu if / else si la cadena con:

 commandMap.get(value).exec(); 

EDITAR

también puede agregar comandos especiales como UnknownCommand o NullCommand , pero necesita un CommandMap que maneje estos casos de esquina para minimizar las comprobaciones del cliente.

Bueno, hay un patrón de comando, pero puede ser excesivo para lo que estás haciendo. Recuerda KISS.

Mi sugerencia sería una especie de combinación ligera de enum y objeto Command. Este es un modismo recomendado por Joshua Bloch en el Ítem 30 de Java Efectivo.

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

Por supuesto, podría pasar parámetros a doCommand o tener tipos de devolución.

Esta solución podría no ser realmente adecuada si las implementaciones de doCommand realmente no “encajan” con el tipo de enumeración, que es, como es habitual cuando hay que hacer una compensación, un poco borroso.

Tener una lista de comandos:

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

Si tiene más de unos pocos comandos, investigue utilizando el patrón Comando, como se responde en otra parte (aunque puede conservar la enumeración e incrustar la llamada a la clase implementadora dentro de la enumeración, en lugar de usar un HashMap). Por favor, vea la respuesta de Andreas o jens a esta pregunta como ejemplo.

Implementar una interfaz como se demuestra de manera sencilla y clara por dfa es clara y elegante (y de manera “oficialmente” compatible). Esto para lo que está diseñado el concepto de interfaz.

En C #, podríamos usar delegates para progtwigdores a quienes les gusta usar punteros functónicos en c, pero la técnica de DFA es la forma de usarlos.

Podrías tener una matriz también

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

Entonces podrías ejecutar un comando por índice

 commands[7].exec(); 

Plagiar de DFA, pero tener una clase base abstracta en lugar de una interfaz. Observe la clave cmd que se usaría más adelante. Por experiencia, me doy cuenta de que con frecuencia un comando de equipo también tiene subcomandos.

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

Construye tus comandos así,

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

A continuación, puede ampliar HashMap genérico o HashTable al proporcionar una función de succión de pares clave-valor:

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

Luego construye tu tienda de comandos:

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

Ahora puedes enviar controles objetivamente

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

Bueno, sugiero crear objetos de comando y ponerlos en un hashmap usando la cadena como clave.

Incluso si creo que el enfoque de patrón de comando es más hacia las mejores prácticas y mantenible a largo plazo, aquí hay una opción de línea de uno para usted:

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

Por lo general trato de resolverlo de esa manera:

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

esto tiene muchas ventajas:

1) no es posible agregar una enumeración sin implementar exec. para que no te pierdas un A.

2) ni siquiera tendrá que agregarlo a ningún mapa de comandos, por lo que no habrá un código repetitivo para construir el mapa. solo el método abstracto y sus implementaciones. (que también es repetitivo, pero no será más corto …)

3) guardará cualquier ciclo de CPU desperdiciado yendo a través de una larga lista de si o calculando hashCodes y haciendo búsquedas.

editar: si no tiene enumeraciones sino cadenas como fuente, simplemente use Command.valueOf(mystr).exec() para llamar al método exec. tenga en cuenta que debe usar el modificador público en la versión ejecutiva si desea llamarlo desde otro paquete.

Probablemente sea mejor que use un Mapa de comandos.

Pero, ¿tienes un conjunto de estos para manejar, terminas con montones de Mapas dando tumbos? Entonces vale la pena mirar hacerlo con Enums.

Puede hacerlo con un Enum sin usar modificadores (probablemente no necesite los getters en el ejemplo), si agrega un método al Enum para resolverlo por “valor”. Entonces puedes hacer:

Actualización: mapa estático agregado para evitar la iteración en cada llamada. Sin vergüenza pellizcado de esta respuesta .

 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 commandsMap; static { commandsMap = new HashMap(); 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(); } } 

La respuesta proporcionada por @dfa es la mejor solución, en mi opinión.

Solo estoy proporcionando algunos fragmentos en caso de que esté utilizando Java 8 y quiera usar Lambdas.

Comando sin parámetros:

 Map commands = new HashMap(); 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(); 

(Podría usar Runnable en lugar de Command, pero no lo considero semánticamente correcto):

Comando con un parámetro:

En caso de que espere un parámetro, puede usar java.util.function.Consumer :

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

En el ejemplo anterior, doSomethingX es un método presente en la clase de myObj que toma cualquier Objeto ( param nombrado en este ejemplo) como argumento.

si tiene múltiples instrucciones “if” imbricadas, este es un patrón para usar un motor de reglas . Ver, por ejemplo, JBOSS Drools .

Solo use un HashMap, como se describe aquí:

si fuera posible tener una serie de procedimientos (lo que usted llamó comandos) que serían útiles.

pero podrías escribir un progtwig para escribir tu código. Todo es muy sistemático si (value = ‘A’) commandA (); más si (…………………… etc.

No estoy seguro de si tiene algún tipo de superposición entre el comportamiento de sus diversos comandos, pero también podría echarle un vistazo al patrón Chain Of Responsibility , que podría proporcionar más flexibilidad al permitir que varios comandos manejen algunos valores de entrada.

Si hace muchas cosas, habrá mucho código, realmente no puedes escapar de eso. Solo hazlo fácil de seguir, dale a las variables nombres muy significativos, los comentarios también pueden ayudar …