Cách tốt hơn để thoát khỏi quá nhiều if / other-if từ đoạn mã sau đây là gì?


13

Tôi đang cố gắng viết một servlet thực hiện nhiệm vụ dựa trên giá trị "hành động" được chuyển thành đầu vào.

Đây là mẫu trong đó

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

     }
    }

Tất cả những gì tôi cần là thoát khỏi quá nhiều if / other-if kiểm tra mã của tôi để dễ đọc hơn và bảo trì mã tốt hơn. Vì vậy, đã cố gắng cho các lựa chọn thay thế khác như

1. trường hợp chuyển đổi - vẫn còn quá nhiều kiểm tra trước khi thực hiện hành động của tôi

2. phản ánh

i] một điều chính là bảo mật - cho phép tôi truy cập ngay cả các biến và phương thức trong lớp mặc dù chỉ định truy cập của nó - tôi không chắc chắn thời tiết tôi có thể sử dụng nó trong mã của mình

ii] và cái khác là mất nhiều thời gian hơn so với kiểm tra if / other-if đơn giản

Có cách tiếp cận nào tốt hơn hoặc thiết kế tốt hơn mà ai đó có thể đề xuất để tổ chức mã trên theo cách tốt hơn không?

EDITED

Tôi đã thêm câu trả lời cho đoạn trích trên xem xét câu trả lời dưới đây .

Tuy nhiên, các lớp sau "ExecutorA" và "ExecutorB" chỉ thực hiện một vài dòng mã. Có phải là một thực hành tốt để thêm chúng như một lớp hơn là thêm nó như một phương thức? Xin tư vấn về vấn đề này.



2
Tại sao bạn quá tải một servlet duy nhất với 9 hành động khác nhau? Tại sao không chỉ đơn giản là ánh xạ mỗi hành động đến một trang khác nhau, được hỗ trợ bởi một servlet khác nhau? Theo cách đó, việc lựa chọn hành động được thực hiện bởi khách hàng và mã máy chủ của bạn chỉ tập trung vào việc phục vụ yêu cầu của khách hàng.
Có lẽ là

Câu trả lời:


12

Dựa trên câu trả lời trước, Java cho phép enums có các thuộc tính để bạn có thể xác định một mẫu chiến lược, đại loại như

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

Sau đó, Executor(Chiến lược) của bạn sẽ là

public interface Executor {
    foo execute();
}

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

Và tất cả if / other trong doPostphương thức của bạn trở thành một cái gì đó như

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

Bằng cách này, bạn thậm chí có thể sử dụng lambdas cho người thi hành trong enum.


cũng nói .. Nhưng tôi cần một sự làm rõ nhỏ .. Tất cả các hành động của tôi action1 (), action2 () sẽ là một vài dòng mã .. sẽ tốt hơn nếu đóng gói nó trong một lớp?
Tom Taylor

4
Đây không phải là số dòng nên thuyết phục bạn tạo các lớp / đối tượng cụ thể, mà thực tế là chúng đại diện cho các hành vi khác nhau. 1 ý tưởng / khái niệm = 1 đối tượng logic.
mgoeminne

2
@RajasubaSubramanian Bạn cũng có thể sử dụng lambda hoặc tham chiếu phương thức nếu bạn cảm thấy rằng một lớp quá nặng. Executorlà (hoặc có thể) một giao diện chức năng.
Hulk

1
@ J.Pichardo Cảm ơn bạn đã cập nhật :) Vì tôi vẫn còn trong java7 nên tôi không thể sử dụng biểu thức lambda .. Vì vậy, hãy theo dõi việc triển khai mô hình chiến lược được đề xuất ở đây tại dzone.com/articles/strargety-potype-im vâyed
Tom Taylor

1
@RajasubaSubramanian tuyệt vời, tôi đã học được một điều mới,
J. Pichardo

7

Thay vì sử dụng sự phản chiếu, hãy sử dụng một giao diện chuyên dụng.

tức là thay vì:

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

Sử dụng

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

Thực hiện từng hành động cho từng hành động và sau đó:

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

Tất nhiên giải pháp này không phải là cao nhất, vì vậy bạn có thể không cần phải đi đến chiều dài đó.


tôi nghĩ nó phải ProcessActionthay vì ActionProcessvậy ...?
Tom Taylor

1
Có tôi đã sửa nó.
Walfrat

1
Và, nói chung, câu trả lời là "sử dụng các cơ chế OOP". Vì vậy, ở đây, bạn nên xác định lại "tình huống" và hành vi liên quan của nó. Nói cách khác, biểu diễn logic của bạn bằng một đối tượng trừu tượng, và sau đó thao tác đối tượng này thay vì các đai ốc và bu lông bên dưới.
mgoeminne

Ngoài ra, một phần mở rộng tự nhiên của cách tiếp cận được đề xuất bởi @Walfrat sẽ bao gồm việc đề xuất một nhà máy (trừu tượng) tạo / trả về ProcessAction đúng tùy thuộc vào tham số Chuỗi được chỉ định.
mgoeminne

@mgoeminne Nghe có vẻ đúng
J. Pichardo

2

Sử dụng Mẫu lệnh , điều này sẽ yêu cầu Giao diện lệnh giống như thế này:

interface CommandInterface {
    CommandInterface execute();
}

Nếu Actionsnhẹ và rẻ để xây dựng thì hãy sử dụng Phương thức xuất xưởng. Tải tên lớp từ tệp thuộc tính ánh xạ actionName=classNamevà sử dụng phương thức xuất xưởng đơn giản để xây dựng các hành động để thực thi.

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

Nếu các Hành động tốn kém để xây dựng thì hãy sử dụng nhóm, chẳng hạn như HashMap ; tuy nhiên, trong hầu hết các trường hợp, tôi đề nghị có thể tránh điều này theo Nguyên tắc Trách nhiệm duy nhất ủy thác phần tử đắt tiền cho một số nhóm tài nguyên chung được xây dựng trước thay vì chính các lệnh.

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

Chúng có thể được thực thi với

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

Đây là một cách tiếp cận rất mạnh mẽ và tách rời, áp dụng SRP, LSP và ISP của các nguyên tắc RẮN . Các lệnh mới không thay đổi mã ánh xạ lệnh. Các lệnh rất đơn giản để thực hiện. Chúng có thể được thêm vào tệp dự án và thuộc tính. Các lệnh nên được đăng ký lại và điều này làm cho nó rất hiệu quả.


1

Bạn có thể sử dụng đối tượng dựa trên phép liệt kê để giảm nhu cầu hardCoding các giá trị chuỗi. Nó sẽ giúp bạn tiết kiệm thời gian và làm cho mã trở nên gọn gàng hơn để đọc và mở rộng trong tương lai.

 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

Mô hình Phương thức nhà máy là những gì tôi tìm kiếm nếu bạn đang tìm kiếm thiết kế có thể mở rộng và ít bảo trì hơn.

Mẫu Phương thức Factory xác định một giao diện để tạo một đối tượng, nhưng hãy để lớp con quyết định lớp nào sẽ khởi tạo. Phương thức Factory cho phép khởi tạo lớp trì hoãn cho lớp con.

 abstract class action {abstract doStuff(action)}

action1, action2 ........ actionN triển khai cụ thể với doStuff Phương thức thực hiện điều cần làm.

Chỉ cần gọi

    action.doStuff(actionN)

Vì vậy, trong tương lai nếu có nhiều hành động được giới thiệu, bạn chỉ cần thêm lớp cụ thể.


typo abstarct -> trừu tượng trong dòng mã đầu tiên. Vui lòng chỉnh sửa. Ngoài ra, bạn có thể thêm một chút mã để xóa ví dụ này để hiển thị trực tiếp hơn cách thức câu trả lời của OP không?
Jay Elston

0

Có tham khảo @J. Câu trả lời của Pichardo tôi đang viết phần sửa đổi đoạn trích trên như sau

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();
  }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.