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


14

Sto provando a scrivere un servlet che svolge attività in base al valore "azione" e lo ha 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 è fuggire da 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. cambia caso - fa ancora troppi controlli prima di fare la mia azione

2. riflessione

i] una cosa principale è la sicurezza - che mi consente di accedere anche alle variabili e ai metodi all'interno della classe nonostante il suo identificatore di accesso - non sono sicuro del tempo che potrei usare nel mio codice

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

Esiste un approccio migliore o una progettazione migliore che qualcuno potrebbe suggerire di organizzare il codice sopra riportato in un modo migliore?

MODIFICATO

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

Tuttavia, le seguenti classi "ExecutorA" e "ExecutorB" eseguono solo poche righe di codice. È una buona pratica aggiungerli come classe piuttosto che aggiungerlo come metodo? Si prega di avvisare al riguardo.



2
Perché stai sovraccaricando un singolo servlet con 9 azioni diverse? Perché non semplicemente mappare ogni azione su una pagina diversa, supportata da un servlet diverso? In questo modo la selezione dell'azione viene eseguita dal client e il codice del server si concentra solo sulla richiesta del client.
Maybe_Factor

Risposte:


13

Basato sulla risposta precedente, Java consente agli enum di avere proprietà in modo da poter definire un modello di strategia, qualcosa del genere

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 la tua 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 doPostmetodo diventa qualcosa di simile

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.


ben detto .. Ma ho bisogno di un piccolo chiarimento .. Tutte le mie azioni action1 (), action2 () sarebbero poche righe di codice .. sarà bene comprimerlo all'interno di una classe?
Tom Taylor

4
Questo non è il numero di righe che dovrebbero convincerti a creare classi / oggetti specifici, ma il fatto che rappresentano comportamenti diversi. 1 idea / concetto = 1 oggetto logico.
mgoeminne,

2
@RajasubaSubramanian Potresti anche usare un riferimento lambda o metodo se ritieni che una classe sia troppo pesante. Executorè (o può essere) un'interfaccia funzionale.
Hulk,

1
@ J.Pichardo Grazie per l'aggiornamento :) Dato che sono ancora in Java7 non ho potuto usare l'espressione lambda .. Quindi, ho seguito l'implementazione enum del modello di strategia suggerito qui su dzone.com/articles/strategy-pattern-implemented
Tom Taylor

1
@RajasubaSubramanian figo, ho imparato qualcosa di nuovo,
J. Pichardo

7

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

cioè invece di:

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

Uso

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

Implementa ciascuno di essi per ciascuna azione e quindi:

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

Naturalmente questa soluzione non è la più leggera, quindi potrebbe non essere necessario andare fino a quella lunghezza.


penso che debba essere ProcessActioninvece di ActionProcessè così ...?
Tom Taylor

1
Sì, l'ho risolto.
Walfrat,

1
E, più in generale, la risposta è "utilizzare i meccanismi OOP". Quindi, qui, dovresti reificare la "situazione" e il suo comportamento associato. In altre parole, rappresentando la tua logica con un oggetto astratto, quindi manipola questo oggetto invece dei suoi dadi e bulloni sottostanti.
mgoeminne,

Inoltre, un'estensione naturale dell'approccio proposto da @Walfrat consisterebbe nel proporre una factory (astratta) che crei / restituisca la ProcessAction corretta in base al parametro String specificato.
mgoeminne,

@mgoeminne Sembra giusto
J. Pichardo,

2

Usa il modello di comando , ciò richiederà un'interfaccia di comando simile a questa:

interface CommandInterface {
    CommandInterface execute();
}

Se Actionssono leggeri ed economici da costruire, utilizzare un metodo di fabbrica. Carica i nomi delle classi da un file delle proprietà che mappa actionName=classNamee 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, utilizzare un pool, ad esempio una mappa hash ; tuttavia, nella maggior parte dei casi, suggerirei che ciò potrebbe essere evitato in base al principio della responsabilità singola delegando l' elemento costoso a un pool di risorse comuni precostruito anziché ai comandi stessi.

    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 solido e disaccoppiato che applica SRP, LSP e ISP dei principi SOLID . I nuovi comandi non cambiano il codice del mapper dei comandi. I comandi sono semplici da implementare. Possono essere appena aggiunti al progetto e al file delle proprietà. I comandi dovrebbero essere rientranti e questo lo rende molto performante.


1

È possibile utilizzare l'oggetto basato sull'enumerazione per ridurre la necessità di hardCoding dei valori di stringa. Ti farà risparmiare un po 'di tempo e renderà il codice molto pulito da leggere ed 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;
      }
  }

1

Il modello del metodo di fabbrica è quello che cerco se stai cercando un design scalabile e meno gestibile.

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

 abstract class action {abstract doStuff(action)}

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

Chiama soltanto

    action.doStuff(actionN)

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


errore di battitura -> abstract nella prima riga di codice. Per favore, modifica. Inoltre, puoi aggiungere un po 'più di codice per cancellare questo esempio per mostrare più direttamente come questo risponde alla domanda del PO?
Jay Elston,

0

Con riferimento a @J. Pichardo risponde che sto scrivendo la modifica dello snippet di cui sopra 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();
  }
}

Non stai creando troppe classi se ci sono troppe azioni. Abbiamo una migliore attuazione.
Vaibhav Sharma
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.