web-dev-qa-db-fra.com

Quelle est la meilleure façon d'échapper à un trop grand nombre if / else-if de l'extrait de code suivant?

J'essaie d'écrire un servlet qui effectue une tâche en fonction de la valeur "action" transmise en entrée.

Voici l'exemple dont

public class SampleClass extends HttpServlet {
     public static void action1() throws Exception{
          //Do some actions
     }
     public static void action2() throws Exception{
          //Do some actions
     }
     //And goes on till action9


     public void doPost(HttpServletRequest req, HttpServletResponse res)throws ServletException, IOException {
          String action = req.getParameter("action");

          /**
           * I find it difficult in the following ways
           * 1. Too lengthy - was not comfortable to read
           * 2. Makes me fear that action1 would run quicker as it was in the top
           * and action9 would run with a bit delay - as it would cross check with all the above if & else if conditions
           */

          if("action1".equals(action)) {
               //do some 10 lines of action
          } else if("action2".equals(action)) {
               //do some action
          } else if("action3".equals(action)) {
               //do some action
          } else if("action4".equals(action)) {
               //do some action
          } else if("action5".equals(action)) {
               //do some action
          } else if("action6".equals(action)) {
               //do some action
          } else if("action7".equals(action)) {
               //do some action
          } else if("action8".equals(action)) {
               //do some action
          } else if("action9".equals(action)) {
               //do some action
          }

          /**
           * So, the next approach i tried it with switch
           * 1. Added each action as method and called those methods from the swith case statements
           */
          switch(action) {
          case "action1": action1();
               break;
          case "action2": action2();
               break;
          case "action3": action3();
               break;
          case "action4": action4();
               break;
          case "action5": action5();
               break;
          case "action6": action6();
               break;
          case "action7": action7();
               break;
          case "action8": action8();
               break;
          case "action9": action9();
               break;
          default:
               break;
          }

          /**
           * Still was not comfortable since i am doing un-necessary checks in one way or the other
           * So tried with [reflection][1] by invoking the action methods
           */
          Map<String, Method> methodMap = new HashMap<String, Method>();

        methodMap.put("action1", SampleClass.class.getMethod("action1"));
        methodMap.put("action2", SampleClass.class.getMethod("action2"));

        methodMap.get(action).invoke(null);  

       /**
        * But i am afraid of the following things while using reflection
        * 1. One is Security (Could any variable or methods despite its access specifier) - is reflection advised to use here?
        * 2. Reflection takes too much time than simple if else
        */

     }
    }

Tout ce dont j'ai besoin est d'échapper à trop de contrôles if/else-if dans mon code pour une meilleure lisibilité et une meilleure maintenance du code. Donc essayé pour d'autres alternatives comme

1. boîtier de commutateur - toujours il fait trop de vérifications avant de faire mon action

2. réflexion

i] une chose principale est la sécurité - qui me permet d'accéder même aux variables et méthodes de la classe malgré son spécificateur d'accès - je ne suis pas sûr que je pourrais l'utiliser dans mon code

ii] et l'autre est que cela prend plus de temps que les simples vérifications if/else-if

Existe-t-il une meilleure approche ou une meilleure conception que quelqu'un pourrait suggérer pour organiser le code ci-dessus d'une meilleure manière?

[~ # ~] modifié [~ # ~]

J'ai ajouté le réponse pour l'extrait ci-dessus considérant la réponse ci-dessous .

Mais encore, les classes suivantes "ExecutorA" et "ExecutorB" ne font que quelques lignes de code. Est-ce une bonne pratique de les ajouter en tant que classe plutôt que de les ajouter en tant que méthode? Veuillez conseiller à cet égard.

14
Tom Taylor

Basé sur la réponse précédente, Java permet aux énumérations d'avoir des propriétés afin que vous puissiez définir un modèle de stratégie, quelque chose comme

public enum Action {
    A ( () -> { //Lambda Sintax
        // Do A
       } ), 
    B ( () -> executeB() ), // Lambda with static method
    C (new ExecutorC()) //External Class 

    public Action(Executor e)
        this.executor = e;
    }

    //OPTIONAL DELEGATED METHOD
    public foo execute() {
        return executor.execute();
    }

    // Action Static Method
    private static foo executeB(){
    // Do B
    }
}

Alors votre Executor (Stratégie) serait

public interface Executor {
    foo execute();
}

public class ExecutorC implements Executor {
    public foo execute(){
        // Do C
    }
}

Et tous vos if/else dans votre méthode doPost deviennent quelque chose comme

public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
    String action = req.getParameter("action");
    Action.valueOf(action).execute();
}

De cette façon, vous pouvez même utiliser des lambdas pour les exécuteurs dans les énumérations.

13
J. Pichardo

Au lieu d'utiliser la réflexion, utilisez une interface dédiée.

c'est-à-dire au lieu de:

      /**
       * Still was not comfortable since i am doing un-necessary checks in one way or the other
       * So tried with [reflection][1] by invoking the action methods
       */
      Map<String, Method> methodMap = new HashMap<String, Method>();

    methodMap.put("action1", SampleClass.class.getMethod("action1"));
    methodMap.put("action2", SampleClass.class.getMethod("action2"));

    methodMap.get(action).invoke(null);  

Utilisation

 public interface ProcessAction{
       public void process(...);
 }

Implémente chacun d'eux pour chaque action puis:

 // as attribute
Map<String, ProcessAction> methodMap = new HashMap<String, ProcessAction>();
// now you can add to the map you can either hardcode them in an init function
methodMap.put("action1",action1Process);

// but if you want some more flexibility you should isolate the map in a class dedicated :
// let's say ActionMapper and add them on init : 

public class Action1Manager{
    private static class ProcessAction1 implements ProcessAction{...}

    public Action1Manager(ActionMapper mapper){
       mapper.addNewAction("action1", new ProcessAction1());
    }
}

Bien sûr, cette solution n'est pas la plus légère, donc vous n'aurez peut-être pas besoin d'aller jusqu'à cette longueur.

7
Walfrat

Utilisez le Command Pattern , cela nécessitera une interface de commande quelque chose comme ceci:

interface CommandInterface {
    CommandInterface execute();
}

Si les Actions sont légers et peu coûteux à construire, utilisez une méthode d'usine. Chargez les noms de classe à partir d'un fichier de propriétés qui mappe actionName=className et utilisez une méthode d'usine simple pour construire les actions à exécuter.

    public Invoker execute(final String targetActionName) {
        final String className = this.properties.getProperty(targetAction);
        final AbstractCommand targetAction = (AbstractCommand) Class.forName(className).newInstance();
        targetAction.execute();
    return this;
}

Si les actions sont coûteuses à construire, utilisez un pool, tel que HashMap ; cependant, dans la plupart des cas, je dirais que cela pourrait être évité en vertu du principe de responsabilité unique déléguer l'élément coûteux à certains pré- construit un pool de ressources communes plutôt que les commandes elles-mêmes.

    public class CommandMap extends HashMap<String, AbstractAction> { ... }

Ceux-ci peuvent ensuite être exécutés avec

    public Invoker execute(final String targetActionName) {
        commandMap.get(targetActionName).execute();
        return this;
}

Il s'agit d'une approche très robuste et découplée qui applique SRP, LSP et ISP des principes SOLID . Les nouvelles commandes ne modifient pas le code du mappeur de commandes. Les commandes sont simples à implémenter. Ils peuvent être simplement ajoutés au projet et au fichier de propriétés. Les commandes doivent être rentrées et cela le rend très performant.

2
Martin Spamer

Le modèle de méthode d'usine est ce que je recherche si vous recherchez une conception évolutive et moins maintenable.

Le modèle de méthode d'usine définit une interface pour créer un objet, mais laisse la sous-classe décider de la classe à instancier. La méthode d'usine permet à une classe de reporter l'instanciation à la sous-classe.

 abstract class action {abstract doStuff(action)}

action1, action2 ........ actionN implémentation concrète avec la méthode doStuff implémentant la chose à faire.

Il suffit d'appeler

    action.doStuff(actionN)

Donc, à l'avenir, si plus d'actions sont introduites, il vous suffit d'ajouter une classe concrète.

1
Anuj Singh

Vous pouvez utiliser l'objet basé sur l'énumération pour réduire le besoin de coder en dur les valeurs de chaîne. Cela vous fera gagner du temps et rendra le code beaucoup plus agréable à lire et à étendre à l'avenir.

 public static enum actionTypes {
      action1, action2, action3....
  }

  public void doPost {
      ...
      switch (actionTypes.valueOf(action)) {
          case action1: do-action1(); break;
          case action2: do-action2(); break;
          case action3: do-action3(); break;
      }
  }
1
Karan

En référence à @J. Pichardo réponse j'écris la modification de l'extrait ci-dessus comme suit

public class SampleClass extends HttpServlet {

public enum Action {
    A (new ExecutorA()),
    B (new ExecutorB())

    Executor executor;

    public Action(Executor e)
        this.executor = e;
    }

    //The delegate method
    public void execute() {
        return executor.execute();
    }
}

public foo Executor {
    foo execute();
}

public class ExecutorA implements Executor{
   public void execute() {
      //Do some action
   }
}

public class ExecutorB implements Executor{
   public void execute() {
      //Do some action
   }
}

public void doPost(HttpServletRequest req, HttpServletResponse res)throws ServletException, IOException {

  String action = req.getParameter("action"); 
  Action.valueOf(action).execute();
  }
}
0
Tom Taylor