web-dev-qa-db-ja.com

次のコードスニペットから多すぎるif / else-ifから脱出するためのより良い方法は何ですか?

入力として渡された「アクション」値に基づいてタスクを実行するサーブレットを作成しようとしています。

これがそのサンプルです

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

     }
    }

私が必要なのは、コードを読みやすくし、コードの保守性を高めるために、コード内のif/else-ifチェックが多すぎないようにすることです。ので、他の代替案を試してみました

1。switch case-それでも私のアクションを実行する前にチェックが多すぎます

2。反射

i]主なものの1つはセキュリティです。これにより、アクセス指定子に関係なく、クラス内の変数やメソッドにもアクセスできます-私のコードでそれを使用できるかどうかはわかりません

ii]もう1つは、単純なif/else-ifチェックよりも時間がかかることです。

上記のコードをより良い方法で整理するために提案できるより良いアプローチやデザインはありますか?

[〜#〜]編集済み[〜#〜]

以下の回答 を考慮して、上記のスニペットに answer を追加しました。

ただし、次のクラス「ExecutorA」と「ExecutorB」は数行のコードしか実行しません。それらをメソッドとして追加するよりも、クラスとして追加することをお勧めしますか?この点について助言してください。

14
Tom Taylor

前の回答に基づいて、Javaは列挙型にプロパティを許可するため、次のような戦略パターンを定義できます

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

次に、あなたのExecutor(戦略)は

public interface Executor {
    foo execute();
}

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

そして、doPostメソッド内のすべてのif/elseは次のようになります

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

このようにして、列挙型のエグゼキュータにラムダを使用することもできます。

13
J. Pichardo

リフレクションを使用する代わりに、専用のインターフェースを使用してください。

つまり、代わりに:

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

使用する

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

各アクションに対してそれらのそれぞれを実装し、次に:

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

もちろん、このソリューションは最も軽いものではないので、その長さまで上げる必要はないかもしれません。

7
Walfrat

コマンドパターン を使用します。これには、次のようなコマンドインターフェイスが必要です。

interface CommandInterface {
    CommandInterface execute();
}

Actionsが軽量で安価に構築できる場合は、ファクトリメソッドを使用します。 actionName=classNameをマップするプロパティファイルからクラス名をロードし、単純なファクトリメソッドを使用して、実行するアクションを作成します。

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

アクションの作成にコストがかかる場合は、 HashMap などのプールを使用します。ただし、ほとんどの場合、これは 単一の責任の原則expensive要素をいくつかの事前に委任することで回避できることをお勧めしますコマンド自体ではなく、共通のリソースプールを構築しました。

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

これらは次に実行することができます

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

これは、SRP、LSP、ISPの SOLID原則 を適用する、非常に堅牢で分離されたアプローチです。新しいコマンドは、コマンドマッパーコードを変更しません。コマンドの実装は簡単です。それらはプロジェクトとプロパティファイルに追加するだけです。コマンドは再入可能である必要があり、これにより非常に高性能になります。

2
Martin Spamer

Factory Methodパターンは、スケーラブルでメンテナンス性の低いデザインを探している場合に私が見るものです。

ファクトリメソッドパターンは、オブジェクトを作成するためのインターフェイスを定義しますが、インスタンス化するクラスをサブクラスに決定させます。ファクトリメソッドを使用すると、クラスはインスタンス化をサブクラスに遅らせることができます。

 abstract class action {abstract doStuff(action)}

action1、action2 ........ actionN doStuffメソッドを使用した具体的な実装。

電話するだけ

    action.doStuff(actionN)

したがって、今後さらにアクションが導入される場合は、具象クラスを追加するだけで済みます。

1
Anuj Singh

列挙型オブジェクトを利用して、文字列値をハードコーディングする必要性を減らすことができます。これにより、時間を節約し、コードを読みやすくして将来拡張できるようになります。

 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
Karan

@Jを参照。 Pichardo answer 上記のスニペットを次のように変更して書いています

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();
  }
}
0
Tom Taylor