Was ist der bessere Weg, um zu vielen if / else-if aus dem folgenden Code-Snippet zu entkommen?

13

Ich versuche, ein Servlet zu schreiben, das eine Aufgabe basierend auf dem Wert "action" ausführt, an den es als Eingabe übergeben wurde.

Hier ist das Beispiel davon

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

     }
    }

Alles, was ich brauche, ist, zu vielen if / else-if-Überprüfungen in meinem Code zu entkommen, um eine bessere Lesbarkeit und eine bessere Codepflege zu gewährleisten. Also versucht für andere Alternativen wie

1. Fall wechseln - es werden immer noch zu viele Überprüfungen durchgeführt, bevor meine Aktion ausgeführt wird

2. Reflexion

i] Eine Hauptsache ist die Sicherheit, mit der ich trotz des Zugriffsspezifizierers sogar auf die Variablen und Methoden innerhalb der Klasse zugreifen kann. Ich bin mir nicht sicher, ob ich sie in meinem Code verwenden kann

ii] und das andere ist, dass es mehr Zeit braucht als die einfachen if / else-if-Prüfungen

Gibt es einen besseren Ansatz oder ein besseres Design, das jemand vorschlagen könnte, um den obigen Code besser zu organisieren?

BEARBEITET

Ich habe die Antwort für das obige Snippet unter Berücksichtigung der folgenden Antwort hinzugefügt .

Die folgenden Klassen "ExecutorA" und "ExecutorB" führen jedoch nur wenige Codezeilen aus. Ist es eine gute Praxis, sie als Klasse hinzuzufügen, als sie als Methode hinzuzufügen? Bitte geben Sie diesbezüglich Hinweise.

Tom Taylor
quelle
1
Mögliches Duplikat von Ansätzen zur Überprüfung mehrerer Bedingungen?
Mücke
2
Warum überladen Sie ein einzelnes Servlet mit 9 verschiedenen Aktionen? Warum nicht einfach jede Aktion einer anderen Seite zuordnen, die von einem anderen Servlet unterstützt wird? Auf diese Weise wird die Auswahl der Aktion vom Client vorgenommen, und Ihr Servercode konzentriert sich nur darauf, die Anforderung des Clients zu bedienen.
Vielleicht_Faktor

Antworten:

12

Basierend auf der vorherigen Antwort ermöglicht Java, dass Aufzählungen Eigenschaften haben, sodass Sie ein Strategiemuster definieren können, wie z

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

Dann wäre deine Executor(Strategie)

public interface Executor {
    foo execute();
}

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

Und all dein Wenn / Sonst in deiner doPostMethode wird so etwas wie

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

Auf diese Weise können Sie sogar Lambdas für die Testamentsvollstrecker in den Aufzählungen verwenden.

J. Pichardo
quelle
gut gesagt .. Aber ich brauche eine kleine Klarstellung .. Alle meine Aktionen action1 (), action2 () wären wenige Codezeilen .. wird es gut sein, es in eine Klasse zu packen?
Tom Taylor
4
Dies ist nicht die Anzahl der Zeilen, die Sie überzeugen sollten, bestimmte Klassen / Objekte zu erstellen, sondern die Tatsache, dass sie unterschiedliche Verhaltensweisen darstellen. 1 Idee / Konzept = 1 logisches Objekt.
Mgoeminne
2
@RajasubaSubramanian Sie können auch eine Lambda- oder Methodenreferenz verwenden, wenn Sie der Meinung sind, dass eine Klasse zu schwer ist. Executorist (oder kann) eine funktionale Schnittstelle.
Hulk
1
@ J.Pichardo Danke für das Update :) Da ich immer noch in Java7 bin, konnte ich keinen Lambda-Ausdruck verwenden. Also folgte die Enum-Implementierung des hier unter dzone.com/articles/strategy-pattern-implemented
Tom Taylor
1
@ RajasubaSubramanian cool, ich habe etwas Neues gelernt,
J. Pichardo
7

Verwenden Sie anstelle der Reflexion eine dedizierte Schnittstelle.

dh anstelle von:

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

Verwenden

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

Implementiert jede von ihnen für jede Aktion und dann:

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

Natürlich ist diese Lösung nicht die leichteste, daher müssen Sie möglicherweise nicht auf diese Länge gehen.

Walfrat
quelle
ich denke es muss ProcessActionstatt ActionProcessist das so ...?
Tom Taylor
1
Ja, ich habe es behoben.
Walfrat
1
Und allgemeiner lautet die Antwort "OOP-Mechanismen verwenden". Hier sollten Sie also die "Situation" und das damit verbundene Verhalten überprüfen. Mit anderen Worten: Stellen Sie Ihre Logik durch ein abstraktes Objekt dar und manipulieren Sie dieses Objekt anstelle der zugrunde liegenden Schrauben und Muttern.
Mgoeminne
Eine natürliche Erweiterung des von @Walfrat vorgeschlagenen Ansatzes würde auch darin bestehen, eine (abstrakte) Factory vorzuschlagen, die abhängig vom angegebenen String-Parameter die richtige ProcessAction erstellt / zurückgibt.
Mgoeminne
@mgoeminne Das klingt ungefähr richtig
J. Pichardo
2

Verwenden Sie das Befehlsmuster . Dies erfordert eine Befehlsschnittstelle wie die folgende:

interface CommandInterface {
    CommandInterface execute();
}

Wenn die Actionsleicht und billig zu bauen sind, verwenden Sie eine Factory-Methode. Laden Sie die Klassennamen aus einer Eigenschaftendatei actionName=className, die die Aktionen für die Ausführung mit einer einfachen Factory-Methode erstellt und erstellt.

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

Wenn die Erstellung der Aktionen teuer ist, verwenden Sie einen Pool, z. B. eine HashMap . In den meisten Fällen würde ich jedoch vorschlagen, dass dies nach dem Prinzip der Einzelverantwortung vermieden werden könnte, indem das teure Element an einen vorkonstruierten gemeinsamen Ressourcenpool delegiert wird und nicht an die Befehle selbst.

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

Diese können dann mit ausgeführt werden

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

Dies ist ein sehr robuster und entkoppelter Ansatz, der SRP, LSP und ISP der SOLID-Prinzipien anwendet . Neue Befehle ändern den Befehlszuordnungscode nicht. Die Befehle sind einfach zu implementieren. Sie können einfach zur Projekt- und Eigenschaftendatei hinzugefügt werden. Die Befehle sollten erneut eingegeben werden, was sie sehr performant macht.

Martin Spamer
quelle
1

Sie können das auf Aufzählung basierende Objekt verwenden, um die Notwendigkeit einer Hardcodierung der Zeichenfolgenwerte zu verringern. Dies spart Ihnen Zeit und macht den Code in Zukunft sehr ordentlich zu lesen und zu erweitern.

 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
quelle
1

Das Muster der Factory-Methode ist das, wonach ich suche, wenn Sie nach skalierbarem und weniger wartbarem Design suchen.

Das Factory-Methodenmuster definiert eine Schnittstelle zum Erstellen eines Objekts. Lassen Sie jedoch die Unterklasse entscheiden, welche Klasse instanziiert werden soll. Mit der Factory-Methode kann eine Klasse die Instanziierung auf die Unterklasse verschieben.

 abstract class action {abstract doStuff(action)}

action1, action2 ........ actionN konkrete Implementierung mit der doStuff-Methode, die die zu erledigenden Aufgaben implementiert.

Ruf einfach an

    action.doStuff(actionN)

Wenn in Zukunft mehr Aktionen eingeführt werden, müssen Sie nur noch eine konkrete Klasse hinzufügen.

Anuj Singh
quelle
Tippfehler abstarct -> Zusammenfassung in der ersten Codezeile. Bitte bearbeiten. Können Sie auch etwas mehr Code hinzufügen, um dieses Beispiel zu löschen und direkter zu zeigen, wie dies die Frage des OP beantwortet?
Jay Elston
0

Mit Bezug auf @J. Pichardo Antwort Ich schreibe das Modifizieren des obigen Snippets wie folgt

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
quelle