Qual è il modo migliore per uscire da troppi if / else-if dal seguente frammento di codice?

7

Sto provando a scrivere un servlet che esegue il task in base al valore "action" passato come input.

Ecco il campione di cui

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
        */

     }
    }

Tutto ciò di cui ho bisogno è di sfuggire a troppi controlli if / else-if nel mio codice per una migliore leggibilità e una migliore manutenzione del codice. Così provato per altre alternative come

1. switch case - fa ancora troppi controlli prima di eseguire la mia azione

2. riflesso

i] una cosa principale è la sicurezza - che mi permette di accedere anche alle variabili e ai metodi all'interno della classe nonostante il suo specificatore di accesso - non sono sicuro che il tempo io possa usarlo nel mio codice

ii] e l'altro è che richiede più tempo dei semplici controlli if / else-if

C'è un approccio migliore o un design migliore qualcuno potrebbe suggerire di organizzare il codice sopra in un modo migliore?

A CURA

Ho aggiunto la risposta per lo snippet precedente considerando la risposta di seguito .

Tuttavia, le seguenti classi "ExecutorA" e "ExecutorB" fanno solo alcune righe di codice. È una buona pratica aggiungerli come una classe piuttosto che aggiungerli come metodo? Si prega di avvisare a questo proposito.

    
posta rm -rf star 06.03.2017 - 09:04
fonte

6 risposte

8

In base alla risposta precedente, Java consente alle enumerazioni di avere proprietà in modo da poter definire un modello di strategia, ad esempio

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

Quindi il tuo Executor (strategia) sarebbe

public interface Executor {
    foo execute();
}

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

E tutto il tuo if / else nel tuo metodo doPost diventa qualcosa come

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

In questo modo potresti persino usare lambda per gli esecutori nelle enumerazioni.

    
risposta data 06.03.2017 - 09:34
fonte
7

Invece di usare la riflessione, usa un'interfaccia dedicata.

cioè anziché:

      /**
       * 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);  

Usa

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

Implementa ciascuno di essi per ciascuna azione e poi:

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

Ovviamente questa soluzione non è la migliore, quindi potresti non aver bisogno di andare fino a quella lunghezza.

    
risposta data 06.03.2017 - 09:37
fonte
1

È possibile utilizzare l'oggetto basato su enumerazione per ridurre la necessità di hardCoding dei valori stringa. Ti farà risparmiare un po 'di tempo e rende il codice molto pulito da leggere & estendere in futuro.

 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;
      }
  }
    
risposta data 06.03.2017 - 09:16
fonte
1

Utilizza il Pattern di comando , ciò richiederà un'interfaccia di comando simile a questa:

interface CommandInterface {
    CommandInterface execute();
}

Se Actions è leggero ed economico da costruire, usa un metodo di fabbrica. Carica i nomi di classe da un file delle proprietà che mappa actionName = className e utilizza un semplice metodo factory per creare le azioni per l'esecuzione.

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

Se le Azioni sono costose da costruire, utilizza un pool, ad esempio un HashMap .

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

Questi possono quindi essere eseguiti con

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

Questo è un approccio molto robusto e disaccoppiato che applica i principi LSP e ISP. I nuovi comandi non cambiano il codice del mappatore di comandi. I comandi sono semplici da implementare. Possono essere aggiunti al progetto e al file delle proprietà. I comandi dovrebbero essere rientranti e questo lo rende molto performante.

    
risposta data 06.04.2017 - 23:16
fonte
0

Con riferimento a @J. Pichardo risposta sto scrivendo la modifica dello snippet precedente come segue

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();
  }
}
    
risposta data 06.03.2017 - 10:06
fonte
0

Il pattern Metodo di fabbrica è quello che guardo se cerchi un design scalabile e meno manutenibile.

Il pattern Metodo di fabbrica definisce un'interfaccia per la creazione di un oggetto, ma lascia che la sottoclasse decida quale classe istanziare. Metodo di fabbrica consente a una classe di rinviare l'istanza alla sottoclasse.

 abstract class action {abstract doStuff(action)}

action1, action2 ........ actionN implementazione concreta con doStuff Method che implementa la cosa da fare.

Basta chiamare

    action.doStuff(actionN)

Quindi, in futuro, se verranno introdotte ulteriori azioni, sarà sufficiente aggiungere una classe concreta.

    
risposta data 06.04.2017 - 21:45
fonte

Leggi altre domande sui tag