¿Cuál es la mejor manera de escapar de muchos si / else-if del siguiente fragmento de código?

7

Estoy intentando escribir un servlet que realiza una tarea basada en el valor de "acción" que se le pasa como entrada.

Aquí está la muestra de lo que

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

     }
    }

Todo lo que necesito es escapar de demasiados if / else-if comprueban mi código para una mejor legibilidad y un mejor mantenimiento del código. Probado para otras alternativas como

1. cambiar el caso - todavía hace demasiadas comprobaciones antes de hacer mi acción

2. reflexión

i] una cosa principal es la seguridad, que me permite acceder incluso a las variables y métodos dentro de la clase a pesar de su especificador de acceso, no estoy seguro de que pueda usarlo en mi código

ii] y la otra es que lleva más tiempo que las simples comprobaciones if / else-if

¿Hay algún mejor enfoque o mejor diseño que alguien pueda sugerir para organizar el código anterior de una mejor manera?

EDITED

He agregado la respuesta para el fragmento de código anterior considerando la siguiente respuesta .

Pero aún así, las siguientes clases "ExecutorA" y "ExecutorB" hacen solo unas pocas líneas de código. ¿Es una buena práctica agregarlos como una clase que agregarlos como un método? Por favor avise a este respecto.

    
pregunta rm -rf star 06.03.2017 - 09:04

6 respuestas

8

Basándose en la respuesta anterior, Java permite que las enumeraciones tengan propiedades para que pueda definir un patrón de estrategia, algo como

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

Entonces tu Executor (Estrategia) sería

public interface Executor {
    foo execute();
}

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

Y todo tu if / else en tu método doPost se convierte en algo así como

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

De esta manera, incluso podría usar lambdas para los ejecutores en las enumeraciones.

    
respondido por el J. Pichardo 06.03.2017 - 09:34
7

En lugar de utilizar la reflexión, use una interfaz dedicada.

es decir, en lugar 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);  

Utilizar

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

Implementa cada una de ellas para cada acción y luego:

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

Por supuesto, esta solución no es la más ligera, por lo que es posible que no tenga que subir a esa longitud.

    
respondido por el Walfrat 06.03.2017 - 09:37
1

Puede utilizar el objeto basado en la enumeración para reducir la necesidad de codificar los valores de cadena. Le ahorrará algo de tiempo y hará que el código esté muy limpio para leer & extender en el 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;
      }
  }
    
respondido por el Karan 06.03.2017 - 09:16
1

Utilice el Patrón de comando , esto requerirá una Interfaz de comando algo como esto:

interface CommandInterface {
    CommandInterface execute();
}

Si los Actions son livianos y económicos de construir, entonces use un Método de Fábrica. Cargue los nombres de clase de un archivo de propiedades que asigne actionName = className y use un método de fábrica simple para crear las acciones para la ejecución.

    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 las Acciones son costosas de construir, entonces use un grupo, como HashMap .

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

Estos pueden ser ejecutados con

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

Este es un enfoque muy robusto y desacoplado que aplica los principios LSP e ISP. Los nuevos comandos no cambian el código del asignador de comandos. Los comandos son simples de implementar. Se pueden añadir al proyecto y al archivo de propiedades. Los comandos deben ser reingresantes y esto lo hace muy eficaz.

    
respondido por el Martin Spamer 06.04.2017 - 23:16
0

Con referencia a @J. Pichardo respuesta escribo el fragmento de modificación anterior como sigue

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();
  }
}
    
respondido por el rm -rf star 06.03.2017 - 10:06
0

El patrón de Método de Fábrica es el aspecto que tengo si buscas un diseño escalable y menos mantenible.

El patrón de método de fábrica define una interfaz para crear un objeto, pero permita que la subclase decida qué clase instanciar. El Método de fábrica permite que una clase difiera la creación de instancias a la subclase.

 abstract class action {abstract doStuff(action)}

action1, action2 ........ actionN implementación concreta con doStuff Method implementando la tarea a realizar.

Solo llama

    action.doStuff(actionN)

Por lo tanto, en el futuro, si se introducen más acciones, solo necesita agregar una clase concreta.

    
respondido por el Anuj Singh 06.04.2017 - 21:45

Lea otras preguntas en las etiquetas