Qual é a melhor maneira de escapar de muitos if / else-if do seguinte snippet de código?

13

Estou tentando escrever um servlet que executa tarefas com base no valor "action" passado como entrada.

Aqui está a amostra da qual

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

     }
    }

Tudo que eu preciso é escapar de muitas verificações if / else-if no meu código para melhor legibilidade e melhor manutenção do código. Então tentei outras alternativas como

1. caso de mudança - ainda faz muitas verificações antes de executar minha ação

2. reflexão

i] uma coisa principal é a segurança - que me permite acessar até as variáveis ​​e métodos dentro da classe, apesar de seu especificador de acesso - não tenho certeza se o tempo poderia ser usado no meu código

ii] e o outro é que leva tempo mais do que as verificações simples de se / outra coisa se

Existe alguma abordagem melhor ou melhor design que alguém possa sugerir para organizar o código acima de uma maneira melhor?

EDITADO

Eu adicionei a resposta para o snippet acima, considerando a resposta abaixo .

Mas ainda assim, as seguintes classes "ExecutorA" e "ExecutorB" executam apenas algumas linhas de código. É uma boa prática adicioná-los como classe do que adicioná-lo como método? Por favor, informe a este respeito.

Tom Taylor
fonte
1
Possível duplicata de abordagens para verificar várias condições?
Gnat #
2
Por que você está sobrecarregando um único servlet com 9 ações diferentes? Por que não mapear simplesmente cada ação para uma página diferente, apoiada por um servlet diferente? Dessa forma, a seleção da ação é feita pelo cliente e o código do servidor se concentra apenas em atender à solicitação do cliente.
Maybe_Factor

Respostas:

12

Com base na resposta anterior, o Java permite que as enums tenham propriedades para que você possa definir um padrão de estratégia, 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
    }
}

Então sua Executor(Estratégia) seria

public interface Executor {
    foo execute();
}

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

E todo o seu if / else no seu doPostmétodo se torna algo como

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

Dessa forma, você pode usar lambdas para os executores nas enumerações.

J. Pichardo
fonte
bem dito .. Mas eu preciso de um pequeno esclarecimento .. Todas as minhas ações action1 (), action2 () seriam poucas linhas de código .. será bom empacotá-lo dentro de uma classe?
Tom Taylor
4
Este não é o número de linhas que devem convencê-lo a criar classes / objetos específicos, mas o fato de que eles representam comportamentos diferentes. 1 idéia / conceito = 1 objeto lógico.
mgoeminne
2
@RajasubaSubramanian Você também pode usar uma referência lambda ou método se sentir que uma classe é muito pesada. Executoré (ou pode ser) uma interface funcional.
Hulk
1
@ J.Pichardo Obrigado pela atualização :) Como ainda estou no java7, não pude usar a expressão lambda. Então, segui a implementação enumérica do
Tom Taylor
1
@RajasubaSubramanian legal, eu aprendi algo novo,
J. Pichardo
7

Em vez de usar a reflexão, use uma interface dedicada.

ou seja, em vez 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);  

Usar

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

Implementa cada um deles para cada ação e, em seguida:

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

É claro que essa solução não é a mais leve, portanto, talvez você não precise ir até esse ponto.

Walfrat
fonte
Eu acho que deve ser, em ProcessActionvez de ActionProcessé assim ...?
Tom Taylor
1
Sim, eu consertei.
Walfrat
1
E, de maneira mais geral, a resposta é "use mecanismos OOP". Então, aqui, você deve reificar a "situação" e seu comportamento associado. Em outras palavras, representando sua lógica por um objeto abstrato e, em seguida, manipule esse objeto em vez de suas porcas e parafusos subjacentes.
mgoeminne
Além disso, uma extensão natural da abordagem proposta por @Walfrat consistiria em propor uma fábrica (abstrata) que cria / retorna o ProcessAction correto, dependendo do parâmetro String especificado.
mgoeminne
@mgoeminne Isso soa certo
J. Pichardo
2

Use o padrão de comando , isso exigirá uma interface de comando mais ou menos assim:

interface CommandInterface {
    CommandInterface execute();
}

Se eles Actionssão leves e baratos de construir, use um Método de Fábrica. Carregue os nomes de classe de um arquivo de propriedades que mapeie actionName=classNamee use um método simples de fábrica para criar as ações para execução.

    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 as ações são caras para construir, use um pool, como um HashMap ; no entanto, na maioria dos casos, eu sugeriria que isso poderia ser evitado sob o Princípio da Responsabilidade Única, delegando o elemento caro a algum pool de recursos comuns pré-construído, em vez dos próprios comandos.

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

Estes podem ser executados com

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

Essa é uma abordagem muito robusta e dissociada que aplica SRP, LSP e ISP dos princípios do SOLID . Novos comandos não alteram o código do mapeador de comandos. Os comandos são simples de implementar. Eles podem ser adicionados apenas ao projeto e ao arquivo de propriedades. Os comandos devem ser reentrantes e isso o torna muito eficiente.

Martin Spamer
fonte
1

Você pode utilizar o objeto baseado em enumeração para reduzir a necessidade de codificar permanentemente os valores da sequência. Isso poupará algum tempo e tornará o código muito interessante de ler e estender no 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;
      }
  }
Karan
fonte
1

O padrão do Método de Fábrica é o que eu vejo se você estiver procurando por um design escalável e menos sustentável.

O padrão Factory Method define uma interface para criar um objeto, mas permite que a subclasse decida qual classe instanciar. O método Factory permite que uma classe adie a instanciação para a subclasse.

 abstract class action {abstract doStuff(action)}

action1, action2 ........ actionN implementação concreta com o método doStuff implementando o que fazer.

Apenas ligue

    action.doStuff(actionN)

Portanto, no futuro, se mais ação for introduzida, você só precisará adicionar uma classe concreta.

Anuj Singh
fonte
typo abstarct -> abstract na primeira linha do código. Por favor edite. Além disso, você pode adicionar um pouco mais de código para liberar este exemplo e mostrar mais diretamente como isso responde à pergunta do OP?
Jay Elston
0

Com referência a @J. Resposta Pichardo, estou escrevendo a modificação do trecho acima, como a seguir

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
fonte