Longue liste d'instructions if en Java

101

Désolée, je ne trouve pas de question répondant à cela, je suis presque certain que quelqu'un d'autre l'a déjà posée.

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

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

Le problème est qu'il y a beaucoup de commandes qui vont rapidement dégénérer en quelque chose hors de contrôle. Horrible à surveiller, pénible à déboguer et époustouflant à comprendre dans quelques mois.

Steve
la source
16
Juste un commentaire - Je recommanderais fortement de prendre le livre de modèles Gang of Four, ou si vous êtes nouveau dans les modèles, le livre Head First Design Patterns in Java (qui est une lecture assez facile et une excellente introduction à un certain nombre de modèles courants ). Les deux sont des ressources précieuses, et les deux ont sauvé mon bacon plus d'une fois.
aperkins le
2
Oui en fait je les possédais mais ils manquent :) C'est pourquoi j'étais sûr que ce que je faisais était mal :) Je n'ai pas pu trouver une solution correcte! Peut-être que cela obtient une bonne position sur Google
Steve
2
C'est juste le modèle de commande lundi ici!
Nick Veys

Réponses:

171

en utilisant le modèle de commande :

public interface Command {
     void exec();
}

public class CommandA() implements Command {

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

// etc etc

puis construisez un Map<String,Command>objet et remplissez-le avec des Commandinstances:

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

alors vous pouvez remplacer votre chaîne if / else if par:

commandMap.get(value).exec();

ÉDITER

vous pouvez également ajouter des commandes spéciales telles que UnknownCommandou NullCommand, mais vous avez besoin d'un CommandMapqui gère ces cas d'angle afin de minimiser les vérifications du client.

dfa
la source
1
... avec la vérification appropriée que commandMap.get () ne renvoie pas null :-)
Brian Agnew
3
bien sûr, j'ai omis un code
standard
10
Au lieu d'un HashMap, vous pouvez utiliser une énumération Java, qui vous donne un ensemble de commandes bien défini au lieu d'une carte molle. Vous pourriez avoir un getter dans l'énumération: Command getCommand (); ou même implémenter exec () comme une méthode abstraite dans l'enum, que chaque instance implémente (enum as command).
JeeBee
2
cela obligera à implémenter toutes les commandes de l'énumération ... ce qui est loin d'être idéal. Avec une interface, vous pouvez également appliquer le modèle Decorator (par exemple DebugCommandDecorator, TraceCommandDecorator), il y a beaucoup plus de flexibilité intégrée dans une interface Java simple
dfa
5
Ok, pour un ensemble de commandes petit et jamais croissant, une énumération est une solution viable.
dfa
12

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

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 peut ne pas vraiment convenir 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.

jens
la source
7

Avoir une énumération 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 de quelques commandes, envisagez d'utiliser 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ération, au lieu d'utiliser un HashMap). Veuillez consulter la réponse d'Andreas ou de jens à cette question pour un exemple.

JeeBee
la source
5
pour chaque nouvelle commande que vous ajoutez, vous devez éditer le commutateur: ce code ne suit pas le principe ouvert / fermé
dfa
Cela dépend du fait que les commandes sont peu nombreuses ou nombreuses, n'est-ce pas? De plus, ce site est tellement lent ces jours-ci qu'il faut 5 tentatives pour modifier une réponse.
JeeBee
ce n'est pas optimal voir stackoverflow.com/questions/1199646/… pour savoir comment faire cela de manière plus optimale.
Andreas Petersson le
Oui, merci d'avoir passé le temps d'implémenter ce que j'ai écrit au bas de mon commentaire - Java Enum as Command Pattern. Si je pouvais modifier mon message, je le mentionnerais, mais ce site est en train de mourir.
JeeBee
Je pense que cette question crie pour une déclaration Switch!
Michael Brown
7

Implémenter une interface comme démontré simplement et clairement par dfa est propre et élégant (et de manière "officiellement" prise en charge). 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 fonction en c, mais la technique de DFA est la manière d'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();

Plagier à partir de 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 d'équipement 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 HashMap ou HashTable générique en fournissant une fonction de succion de paire 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();
Geek béni
la source
+1 pour mentionner les délégués au cas où des personnes .NET verraient cette question et deviendraient fous avec les interfaces à méthode unique. Mais ils ne sont vraiment pas comparables aux pointeurs de fonction. Ils sont plus proches d'une version prise en charge par la langue du modèle de commande.
Daniel Earwicker
5

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

keuleJ
la source
3

Même si je pense que l'approche du modèle de commande est plus orientée vers les meilleures pratiques et maintenable à long terme, voici une option unique pour vous:

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

Svachon
la source
2

J'essaye 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. pour ne pas manquer un A.

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

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

edit: si vous n'avez pas d'énumérations mais des chaînes comme source, utilisez simplement Command.valueOf(mystr).exec()pour 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.

Andreas Petersson
la source
2

Il vaut probablement mieux utiliser une carte de commandes.

Mais si vous avez un ensemble de ces éléments à gérer, vous vous retrouvez avec des tonnes de cartes qui frappent. Ensuite, cela vaut la peine de le faire avec Enums.

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

Mise à jour: ajout d'une carte statique pour éviter l'itération à chaque appel. Sans vergogne pincé de 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();
    }
}
Vendeur riche
la source
2

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

Je fournis juste quelques extraits au cas où vous utiliseriez Java 8 et voudriez 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 au lieu de Command, mais je ne le considère pas comme sémantiquement correct):

Commande avec un paramètre:

Au cas où 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, doSomethingXest une méthode présente dans myObjla classe de qui prend n'importe quel objet (nommé paramdans cet exemple) comme argument.

Marlon Bernardes
la source
1

si vous avez plusieurs instructions imbriquées 'if', il s'agit d'un modèle pour l'utilisation d'un moteur de règles . Voir, par exemple, JBOSS Drools .

Pierre
la source
0

s'il était possible d'avoir un tableau de procédures (ce que vous avez appelé des commandes) qui seraient utiles ..

mais vous pouvez écrire un programme pour écrire votre code. Tout est très systématique if (value = 'A') commandA (); sinon si (........................ etc

user147042
la source
0

Je ne sais pas si vous avez un chevauchement entre le comportement de vos différentes commandes, mais vous voudrez peut-être également jeter un coup d'œil au modèle Chain Of Responsibility qui pourrait offrir plus de flexibilité en permettant à plusieurs commandes de gérer certaines valeurs d'entrée.

étain
la source
0

Le modèle de commande est la voie à suivre. Voici un exemple utilisant java 8:

1. Définissez l'interface:

public interface ExtensionHandler {
  boolean isMatched(String fileName);
  String handle(String fileName);
}

2. Implémentez l'interface avec chacune des extensions:

public class PdfHandler implements ExtensionHandler {
  @Override
  public boolean isMatched(String fileName) {
    return fileName.endsWith(".pdf");
  }

  @Override
  public String handle(String fileName) {
    return "application/pdf";
  }
}

et

public class TxtHandler implements ExtensionHandler {
  @Override public boolean isMatched(String fileName) {
    return fileName.endsWith(".txt");
  }

  @Override public String handle(String fileName) {
    return "txt/plain";
  }
}

etc .....

3. Définissez le client:

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. Et voici le résultat de l'exemple:

  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"
  }
nxhoaf
la source
-1

S'il fait beaucoup de choses, alors il y aura beaucoup de code, vous ne pouvez pas vraiment vous en éloigner. Simplifiez-vous le suivi, donnez aux variables des noms très significatifs, les commentaires peuvent aussi aider ...

marque
la source