Question Longue liste des déclarations if en Java


Désolé je ne peux pas trouver une question répondant à cela, je suis presque certain que quelqu'un d'autre l'a soulevé avant.

Mon problème est que j'écris des bibliothèques système pour exécuter des périphériques intégrés. J'ai des commandes qui peuvent être envoyées à ces appareils via des émissions radio. Cela ne peut être fait que par du texte. dans les bibliothèques système, j'ai un thread qui gère les commandes qui ressemble à ceci

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

Le problème, c’est qu’il ya beaucoup de commandes qui vont rapidement aboutir à quelque chose de hors de contrôle. Horrible à regarder, pénible à déboguer et ahurissant à comprendre dans quelques mois.


95
2017-07-29 11:40


origine


Réponses:


en utilisant Modèle de commande:

public interface Command {
     void exec();
}

public class CommandA() implements Command {

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

// etc etc

puis construire un Map<String,Command> objet et le remplir avec Command instances:

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

alors vous pouvez remplacer votre si/sinon si chaîne avec:

commandMap.get(value).exec();

MODIFIER

vous pouvez également ajouter des commandes spéciales telles que UnknownCommand ou NullCommand, mais vous avez besoin d'un CommandMap qui gère ces cas en coin afin de minimiser les contrôles du client.


165
2017-07-29 11:45



Bien il y a un modèle de commande mais cela peut être exagéré pour ce que vous faites. Rappelez-vous KISS.


22
2017-07-29 11:43



Ma suggestion serait une sorte de combinaison légère d’énumération et d’objet Command. Ceci est un idiome recommandé par Joshua Bloch dans l'article 30 de Java efficace.

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

Bien sûr, vous pouvez passer des paramètres à doCommand ou avoir des types de retour.

Cette solution n'est peut-être pas vraiment adaptée si les implémentations de doCommand ne correspondent pas vraiment au type enum, ce qui est - comme d'habitude lorsque vous devez faire un compromis - un peu flou.


12
2017-07-29 12:09



Avoir une liste de commandes:

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 vous avez plus que quelques commandes, utilisez le modèle de commande, comme indiqué ailleurs (bien que vous puissiez conserver l'énumération et incorporer l'appel à la classe d'implémentation dans l'énum au lieu d'utiliser une HashMap). S'il vous plaît voir la réponse d'Andreas ou Jens à cette question pour un exemple.


7
2017-07-29 11:47



L'implémentation d'une interface telle que démontrée simplement et simplement par dfa est propre et élégante (et prise en charge "officiellement"). C'est à cela que sert le concept d'interface.

En C #, nous pourrions utiliser des délégués pour les programmeurs qui aiment utiliser des pointeurs de functon dans c, mais la technique de DFA est la manière de l'utiliser.

Vous pourriez aussi avoir un tableau

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

Ensuite, vous pouvez exécuter une commande par index

commands[7].exec();

Plagiat des DFA, mais ayant une classe de base abstraite au lieu d'une interface. Notez la cmdKey qui sera utilisée plus tard. Par expérience, je me rends compte que, souvent, une commande de matériel a aussi des sous-commandes.

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

Construisez vos commandes ainsi,

public class CommandA
extends Command
{
  public CommandA(String subCmd)
  {
    this.cmdKey = "A";
    this.subCmd = subCmd;
  }

  public byte exec()
  {
    sendWhatever(...);
    byte status = receiveWhatever(...);
    return status;
  }
}

Vous pouvez ensuite étendre les applications génériques HashMap ou HashTable en fournissant une fonction d'aspiration de paires clé-valeur:

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

Ensuite, construisez votre magasin de commandes:

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

Maintenant, vous pouvez envoyer des contrôles objectivement

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

7
2017-07-30 02:42



Eh bien, je suggère de créer des objets de commande et de les placer dans un hashmap en utilisant la chaîne en tant que clé.


5
2017-07-29 11:46



Même si je crois que l’approche des modèles de commande est davantage axée sur les meilleures pratiques et la maintenance à long terme, voici une option unique:

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


3
2017-07-29 12:00



J'essaie généralement de le résoudre de cette façon:

public enum Command {

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

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

abstract void exec();
 }

cela présente de nombreux avantages:

1) il n'est pas possible d'ajouter une énumération sans implémenter exec. donc vous ne manquerez pas un A.

2) vous n'aurez même pas à l'ajouter à une carte de commande, donc pas de code passe-partout pour la construction de la carte. juste la méthode abstraite et ses implémentations. (ce qui est sans doute aussi passe-partout, mais il ne sera pas plus court ..)

3) vous économiserez tous les cycles de CPU perdus en parcourant une longue liste de si ou en calculant des hashCodes et en effectuant des recherches.

modifier: si vous n'avez pas de énumérations mais des chaînes comme source, utilisez simplement Command.valueOf(mystr).exec() appeler la méthode exec. Notez que vous devez utiliser le modificateur public sur exec si vous souhaitez l'appeler à partir d'un autre package.


2
2017-07-29 12:02



Vous devriez probablement utiliser une carte de commandes.

Mais si vous en avez un ensemble à gérer, vous vous retrouvez avec des tas de cartes qui heurtent. Alors ça vaut la peine de le faire avec Enums.

Vous pouvez le faire avec un Enum sans utiliser les commutateurs (vous n'avez probablement pas besoin des getters dans l'exemple), si vous ajoutez une méthode à Enum pour résoudre pour "value". Alors vous pouvez juste faire:

Mise à jour: ajout d'une carte statique pour éviter l'itération à chaque appel. Sans vergogne cette réponse.

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

2
2017-07-29 12:26



La réponse fournie par @dfa est la meilleure solution, à mon avis.

Je fournis juste quelques extraits au cas où vous utilisez Java 8 et veux utiliser Lambdas!

Commande sans paramètres:

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

(vous pouvez utiliser un Runnable à la place de Command, mais je ne le considère pas comme sémantiquement correct):

Commande avec un paramètre:

Si vous attendez un paramètre que vous pourriez utiliser 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);

Dans l'exemple ci-dessus, doSomethingX est une méthode présente dans myObjla classe qui prend n'importe quel objet (nommé param dans cet exemple) comme argument.


2
2017-07-02 22:17