次のコードスニペットからあまりにも多くのif / else-ifから脱出するより良い方法は何ですか?


13

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

サンプルは次のとおりです

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. ケースの切り替え -それでもアクションを実行する前に多くのチェックを行う

2. リフレクション

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

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

上記のコードをより良い方法で整理するために提案できる、より良いアプローチや設計がありますか?

編集済み

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

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



2
単一のサーブレットに9つの異なるアクションをオーバーロードしているのはなぜですか?各アクションを異なるサーブレットに裏付けられた異なるページに単純にマッピングしないのはなぜですか?このように、アクションの選択はクライアントによって行われ、サーバーコードはクライアントのリクエストの処理に焦点を合わせます。
-Maybe_Factor

回答:


12

前の回答に基づいて、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();
}

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


よく言った..しかし、私は少し明確にする必要があります。私のアクションaction1()、action2()はすべてコードの数行になります。
トムテイラー

4
これは、特定のクラス/オブジェクトを作成するように説得する行数ではなく、それらが異なる動作を表すという事実です。1アイデア/コンセプト= 1論理オブジェクト。
mgoeminne

2
@RajasubaSubramanianクラスが重すぎると感じる場合は、ラムダまたはメソッド参照を使用することもできます。Executor機能的なインターフェースです(または、そうすることができます)。
ハルク

1
@ J.Pichardo更新してくれてありがとう:)私はまだjava7にいるので、ラムダ式を使用できませんでした。そこで、ここで提案されている戦略パターンの列挙実装に従ってください。dzone.com/ articles / strategy
トムテイラー

1
@RajasubaSubramanianクール、私は何か新しいこと、学んだ
J. Pichardo

7

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

すなわち:の代わりに:

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

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


私はそれがあることが必要だと思うProcessActionの代わりにActionProcessなるようですか...?
トムテイラー

1
はい、修正しました。
ウォルフラット

1
そして、より一般的には、答えは「use OOPメカニズム」です。したがって、ここでは、「状況」とそれに関連する動作を具体化する必要があります。つまり、抽象オブジェクトでロジックを表現し、その下にあるナットやボルトの代わりにこのオブジェクトを操作します。
mgoeminne

また、@ Walfratによって提案されたアプローチの自然な拡張は、指定されたStringパラメーターに応じて適切なProcessActionを作成/返す(抽象)ファクトリーを提案することにあります。
mgoeminne

権利について@mgoeminneその音
J. Pichardo

2

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

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などのプールを使用します。ただし、ほとんどの場合、コマンド自体ではなく、事前に構築された共通リソースプールに高価な要素を委任する単一責任原則の下でこれを回避できることをお勧めします。

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

これらはその後で実行できます

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

これは、SRPID原則の SRP、LSP、およびISPを適用する非常に堅牢で分離されたアプローチです。新しいコマンドは、コマンドマッパーコードを変更しません。コマンドの実装は簡単です。プロジェクトとプロパティファイルに追加するだけです。コマンドはリエントラントである必要があり、これにより非常にパフォーマンスが向上します。


1

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

 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

Factory Methodパターンは、スケーラブルで保守性の低い設計を探している場合に見られるものです。

Factory Methodパターンは、オブジェクトを作成するためのインターフェースを定義しますが、サブクラスにインスタンス化するクラスを決定させます。ファクトリメソッドにより、クラスはインスタンス化をサブクラスに延期できます。

 abstract class action {abstract doStuff(action)}

action1、action2 ........ actionN実行することを実装するdoStuffメソッドによる具体的な実装。

電話するだけ

    action.doStuff(actionN)

そのため、今後さらにアクションが導入された場合は、具体的なクラスを追加するだけで済みます。


typo abstarct->最初のコード行の抽象。編集してください。また、この例をフラッシュしてもう少しコードを追加して、OPの質問にどのように答えるかをより直接的に示すことができますか?
ジェイエルストン

0

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

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();
  }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.