다음 코드 스 니펫에서 너무 많은 if / else-if를 피하는 더 좋은 방법은 무엇입니까?


14

"액션"값을 기반으로 작업을 수행하는 서블릿을 입력으로 전달하려고합니다.

다음은 샘플입니다

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] 한 가지 중요한 것은 보안입니다. 액세스 지정자에도 불구하고 클래스 내의 변수 및 메소드에도 액세스 할 수 있습니다. 날씨에 따라 코드에서 사용할 수 있는지 확실하지 않습니다.

ii] 그리고 다른 하나는 간단한 if / else-if 검사보다 시간이 더 걸린다는 것입니다

위의 코드를 더 나은 방식으로 구성하도록 제안 할 수있는 더 나은 접근 방법이나 더 나은 디자인이 있습니까?

편집

아래 답변을 고려 하여 위의 스 니펫에 대한 답변 을 추가했습니다 .

그러나 여전히 다음 클래스 "ExecutorA"및 "ExecutorB"는 몇 줄의 코드 만 수행합니다. 메소드로 추가하는 것보다 클래스로 추가하는 것이 좋은 습관입니까? 이와 관련하여 조언하십시오.



2
왜 9 가지 다른 액션으로 단일 서블릿을 오버로드합니까? 각 서블릿을 다른 서블릿이 지원하는 다른 페이지에 간단히 매핑 해 보시겠습니까? 이렇게하면 클라이언트가 작업을 선택할 수 있으며 서버 코드는 클라이언트 요청을 처리하는 데 중점을 둡니다.
Maybe_Factor

답변:


13

이전 답변을 기반으로 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
    }
}

그리고 방법의 모든 if / else는 doPost다음과 같이됩니다.

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

이렇게하면 열거 형의 실행 프로그램에 람다를 사용할 수도 있습니다.


잘 말했다 .. 그러나 나는 작은 설명이 필요하다. 모든 나의 행동 action1 (), action2 ()는 몇 줄의 코드 일 것이다.
Tom Taylor

4
이것은 특정 클래스 / 객체를 만들도록 설득해야하는 줄의 수가 아니라 다른 행동을 나타냅니다. 1 아이디어 / 개념 = 1 논리 객체.
mgoeminne

2
@RajasubaSubramanian 클래스가 너무 무겁다 고 생각되면 람다 또는 메서드 참조를 사용할 수도 있습니다. Executor기능적 인터페이스입니다.
Hulk

1
업데이트 : 내가 java7 여전히 해요 때문에 나는 람다 표현식을 사용하지 수에 대한 J.Pichardo 감사 @ .. 그래서는 여기 제안 전략 패턴의 열거 구현을 따라 dzone.com/articles/strategy-pattern-implemented
Tom Taylor

1
@RajasubaSubramanian cool, 나는 새로운 것을 배웠습니다
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...?
Tom Taylor

1
예, 고쳤습니다.
Walfrat

1
보다 일반적으로 대답은 "Use OOP 메커니즘"입니다. 따라서 여기에서 "상황"과 관련 동작을 수정해야합니다. 즉, 추상 객체로 논리를 표현한 다음 기본 너트와 볼트 대신이 객체를 조작합니다.
mgoeminne

또한 @Walfrat가 제안한 접근 방식의 자연스러운 확장은 지정된 String 매개 변수에 따라 올바른 ProcessAction을 생성 / 반환하는 (추상적 인) 팩토리를 제안하는 것으로 구성됩니다.
mgoeminne

@mgoeminne 바로 그 소리
J. Pichardo

2

Command Pattern을 사용하면 다음과 같은 명령 인터페이스가 필요합니다.

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

Actions를 빌드하는 데 비용이 많이 드는 경우 HashMap 과 같은 풀을 사용하십시오 . 그러나 대부분의 경우 고가의 요소를 명령 자체가 아닌 사전 구성된 공통 리소스 풀로 위임하는 단일 책임 원칙 하에서 이러한 문제를 피할 수 있다고 제안 합니다.

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

이것들은 다음으로 실행될 수 있습니다

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

이는 SOLID 원칙 의 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 패턴은 확장 가능하고 유지 관리가 용이하지 않은 디자인을 찾고 있다면 내가 보는 것입니다.

팩토리 메소드 패턴은 객체를 생성하기위한 인터페이스를 정의하지만 서브 클래스가 인스턴스화 할 클래스를 결정하도록합니다. 팩토리 메소드는 클래스가 인스턴스화를 서브 클래스로 연기하도록합니다.

 abstract class action {abstract doStuff(action)}

doStuff 메소드를 사용하여 action1, action2 ........ actionN 구체적 구현을 ​​수행합니다.

그냥 전화

    action.doStuff(actionN)

따라서 앞으로 더 많은 조치가 도입되면 구체적인 클래스를 추가하기 만하면됩니다.


첫 번째 코드 라인에서 typo abstarct-> abstract 수정하십시오. 또한이 예제를 플러시하여 OP의 질문에 어떻게 대답하는지 더 직접 보여주기 위해 코드를 조금 더 추가 할 수 있습니까?
Jay Elston

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

액션이 너무 많으면 클래스를 너무 많이 만들지 않습니다. 더 나은 구현이 있습니까?
Vaibhav Sharma
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.