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

14

J'essaie d'écrire une 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. changer de boîtier - il fait encore 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?

ÉDITÉ

J'ai ajouté la réponse pour l'extrait ci-dessus en tenant compte de 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.

Tom Taylor
la source
1
Duplication possible d' approches pour vérifier plusieurs conditions?
moucher
2
Pourquoi surchargez-vous un seul servlet avec 9 actions différentes? Pourquoi ne pas simplement mapper chaque action sur une page différente, soutenue par un servlet différent? De cette façon, la sélection de l'action est effectuée par le client et le code de votre serveur se concentre uniquement sur la réponse à la demande du client.
Maybe_Factor

Réponses:

13

Sur la base de la réponse précédente, Java permet aux enums 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 doPostméthode 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 lambdas pour les exécuteurs dans les énumérations.

J. Pichardo
la source
bien dit .. Mais j'ai besoin d'une petite clarification .. Toutes mes actions action1 (), action2 () seraient quelques lignes de code .. sera-t-il bon de les emballer dans une classe?
Tom Taylor
4
Ce n'est pas le nombre de lignes qui devrait vous convaincre de créer des classes / objets spécifiques, mais le fait qu'elles représentent des comportements différents. 1 idée / concept = 1 objet logique.
mgoeminne
2
@RajasubaSubramanian Vous pouvez également utiliser un lambda ou une référence de méthode si vous pensez qu'une classe est trop lourde. Executorest (ou peut être) une interface fonctionnelle.
Hulk
1
@ J.Pichardo Merci pour la mise à jour :) Puisque je suis encore en java7, je ne pouvais pas utiliser l'expression lambda .. Donc, j'ai suivi l'implémentation de l'énumération du modèle de stratégie suggéré ici sur dzone.com/articles/strategy-pattern-implemented
Tom Taylor
1
@RajasubaSubramanian cool, j'ai appris quelque chose de nouveau,
J. Pichardo
7

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, vous n'aurez donc pas besoin d'aller jusqu'à cette longueur.

Walfrat
la source
Je pense que ça doit être ProcessActionau lieu de ActionProcessça, alors ...?
Tom Taylor
1
Oui je l'ai réparé.
Walfrat
1
Et, plus généralement, la réponse est "utiliser des mécanismes de POO". Donc, ici, vous devez réifier la "situation" et son comportement associé. En d'autres termes, représenter votre logique par un objet abstrait, puis manipuler cet objet au lieu de ses écrous et boulons sous-jacents.
mgoeminne
De plus, une extension naturelle de l'approche proposée par @Walfrat consisterait à proposer une fabrique (abstraite) qui crée / renvoie la bonne ProcessAction en fonction du paramètre String spécifié.
mgoeminne
@mgoeminne Ça sonne à droite
J. Pichardo
2

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

interface CommandInterface {
    CommandInterface execute();
}

Si la construction Actionsest légère et bon marché, utilisez une méthode d'usine. Chargez les noms de classe à partir d'un fichier de propriétés qui mappe actionName=classNameet 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 qu'un HashMap ; cependant, dans la plupart des cas, je dirais que cela pourrait être évité en vertu du principe de responsabilité unique en déléguant l' élément coûteux à un pool de ressources communes préconstruit plutôt qu'aux 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 les principes 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 fichier de projet et de propriétés. Les commandes doivent être réentrantes et cela le rend très performant.

Martin Spamer
la source
1

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;
      }
  }
Karan
la source
1

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.

Anuj Singh
la source
typo abstarct -> abstract dans la première ligne de code. Veuillez modifier. De plus, pouvez-vous ajouter un peu plus de code pour débusquer cet exemple afin de montrer plus directement comment cela répond à la question du PO?
Jay Elston
0

En référence à @J. Pichardo répond que 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();
  }
}
Tom Taylor
la source
Ne créez-vous pas trop de classes s'il y a trop d'actions. Avons-nous une meilleure mise en œuvre.
Vaibhav Sharma