วิธีที่ดีกว่าในการหลีกเลี่ยงจากมากเกินไปถ้า / else-if จากตัวอย่างโค้ดต่อไปนี้คืออะไร


14

ฉันพยายามเขียน servlet ซึ่งทำงานตามค่า "การกระทำ" ที่ส่งผ่านไปเป็นอินพุต

นี่คือตัวอย่างที่

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

     }
    }

ทั้งหมดที่ฉันต้องการคือการหลีกเลี่ยงจากมากเกินไปถ้า / else- ถ้าการตรวจสอบในรหัสของฉันสำหรับการอ่านที่ดีขึ้นและการบำรุงรักษารหัสที่ดีขึ้น ดังนั้นลองสำหรับทางเลือกอื่น ๆ เช่น

1. สลับตัวพิมพ์ - ยังคงตรวจสอบมากเกินไปก่อนที่จะทำการกระทำของฉัน

2. การสะท้อนกลับ

i] สิ่งสำคัญประการหนึ่งคือความปลอดภัย - ซึ่งช่วยให้ฉันสามารถเข้าถึงแม้กระทั่งตัวแปรและวิธีการภายในชั้นเรียนแม้จะมีตัวระบุการเข้าถึง - ฉันไม่แน่ใจว่าสภาพอากาศฉันสามารถใช้มันในรหัสของฉันได้อย่างไร

ii] และอีกอย่างหนึ่งคือใช้เวลานานกว่าการตรวจสอบ if / else-if แบบง่าย

มีวิธีใดที่ดีกว่าหรือมีการออกแบบที่ดีกว่าบางคนสามารถแนะนำให้จัดระเบียบโค้ดข้างต้นด้วยวิธีที่ดีกว่าหรือไม่?

แก้ไข

ผมได้เพิ่มคำตอบสำหรับข้อมูลโค้ดข้างต้นพิจารณาดังต่อไปนี้คำตอบ

แต่ถึงกระนั้นคลาสต่อไปนี้ "ExecutorA" และ "ExecutorB" ทำโค้ดเพียงไม่กี่บรรทัด เป็นการดีที่จะเพิ่มพวกเขาเป็นคลาสมากกว่าการเพิ่มเป็นวิธีการหรือไม่? กรุณาแนะนำในเรื่องนี้



2
เพราะเหตุใดคุณจึงโหลดเซิร์ฟเล็ตมากเกินไปด้วย 9 การกระทำที่แตกต่างกัน ทำไมไม่แมปการดำเนินการแต่ละอย่างกับเพจอื่นโดยใช้เซิร์ฟเล็ตอื่น การเลือกวิธีการกระทำนั้นกระทำโดยไคลเอนต์และรหัสเซิร์ฟเวอร์ของคุณเพียงมุ่งเน้นที่การให้บริการตามคำขอของลูกค้า
อาจจะ_Factor

คำตอบ:


13

จากคำตอบก่อนหน้านี้ Java อนุญาตให้ enums มีคุณสมบัติเพื่อให้คุณสามารถกำหนดรูปแบบกลยุทธ์ได้

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วิธีการของคุณกลายเป็นเหมือน

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

วิธีนี้คุณสามารถใช้ lambdas สำหรับผู้บริหารใน enums ได้


พูดได้ดี .. แต่ฉันต้องการความกระจ่างเล็กน้อย .. การกระทำทั้งหมดของฉัน action1 (), action2 () จะเป็นโค้ดไม่กี่บรรทัด .. มันจะดีไหมถ้าจะบรรจุในคลาส?
Tom Taylor

4
นี่ไม่ใช่จำนวนบรรทัดที่ควรโน้มน้าวให้คุณสร้างคลาส / วัตถุเฉพาะ แต่ความจริงที่ว่าพวกเขาแสดงพฤติกรรมที่แตกต่างกัน 1 ความคิด / แนวคิด = 1 วัตถุตรรกะ
mgoeminne

2
@RajasubaSubramanian คุณสามารถใช้แลมบ์ดาหรือการอ้างอิงวิธีการได้ถ้าคุณรู้สึกว่าคลาสนั้นหนักเกินไป Executoris (หรือสามารถ) ส่วนต่อประสานการทำงาน
Hulk

1
@ J.Pichardo ขอขอบคุณสำหรับการปรับปรุง :) ตั้งแต่ฉันยังคงอยู่ใน java7 ฉันไม่สามารถใช้การแสดงออกแลมบ์ดา .. ดังนั้นตามการดำเนินงาน enum ของรูปแบบกลยุทธ์แนะนำที่นี่ที่dzone.com/articles/strategy-pattern-implemented
Tom Taylor

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...
Tom Taylor

1
ใช่ฉันแก้ไขแล้ว
Walfrat

1
และโดยทั่วไปคำตอบก็คือ "ใช้กลไก OOP" ดังนั้นที่นี่คุณควรระบุ "สถานการณ์" และพฤติกรรมที่เกี่ยวข้องอีกครั้ง ในคำอื่น ๆ ที่แสดงถึงตรรกะของคุณโดยวัตถุนามธรรมแล้วจัดการวัตถุนี้แทนถั่วและสลักเกลียว
mgoeminne

นอกจากนี้ส่วนขยายตามธรรมชาติของวิธีการที่เสนอโดย @Walfrat จะประกอบด้วยในการเสนอโรงงาน (นามธรรม) ที่สร้าง / ส่งกลับ 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;
}

นี้เป็นวิธีการที่มีประสิทธิภาพมากและแยกที่ใช้ SRP, LSP และ ISP ของหลักการ SOLID คำสั่งใหม่ไม่เปลี่ยนรหัส mapper คำสั่ง คำสั่งนั้นใช้งานง่าย สามารถเพิ่มลงในไฟล์โครงการและคุณสมบัติได้ คำสั่งควรเข้าใหม่และสิ่งนี้ทำให้มันมีประสิทธิภาพมาก


1

คุณสามารถใช้วัตถุตามการแจงนับเพื่อลดความต้องการ hardCoding ค่าสตริง มันจะช่วยให้คุณประหยัดเวลาและทำให้รหัสเรียบร้อยอ่านและขยายในอนาคต

 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

รูปแบบวิธีการจากโรงงานคือสิ่งที่ฉันดูถ้าคุณกำลังมองหาการออกแบบที่ปรับขนาดได้และบำรุงรักษาน้อย

รูปแบบวิธีการจากโรงงานกำหนดอินเทอร์เฟซสำหรับการสร้างวัตถุ แต่ให้คลาสย่อยตัดสินใจว่าคลาสใดที่จะสร้างอินสแตนซ์ วิธีการจากโรงงานช่วยให้การเลื่อนชั้นเรียนเลื่อนระดับเป็นคลาสย่อย

 abstract class action {abstract doStuff(action)}

action1, action2 ........ actionN การนำคอนกรีตไปปฏิบัติด้วยวิธี doStuff

เพียงแค่โทร

    action.doStuff(actionN)

ดังนั้นในอนาคตหากมีการดำเนินการเพิ่มเติมคุณต้องเพิ่มคลาสที่เป็นรูปธรรม


abstarct typo -> นามธรรมในบรรทัดรหัสแรก โปรดแก้ไข นอกจากนี้คุณสามารถเพิ่มรหัสอีกเล็กน้อยเพื่อล้างตัวอย่างนี้เพื่อแสดงวิธีการตอบคำถามของ 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.