Який кращий спосіб позбутися від занадто багатьох if / else - якщо з наступного фрагмента коду?


14

Я намагаюся написати сервлет, який виконує завдання на основі значення "action", переданого йому як вхідне.

Ось зразок якого

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 - якщо чеків у моєму коді для кращої читабельності та кращого обслуговування коду. Тож спробував для інших альтернатив, як

1. Перемикайте корпус - все одно він робить занадто багато перевірок, перш ніж робити мою дію

2. рефлексія

i] одне головне - це безпека - яка дозволяє мені отримати доступ навіть до змінних та методів у класі, не дивлячись на його специфікатор доступу - я не впевнений, що погоду я міг би використати у своєму коді

ii] та інше - це потребує часу більше, ніж прості перевірки if / else-if

Чи є кращий підхід чи кращий дизайн, який би хтось міг запропонувати краще організувати вищезазначений код?

ВИДАЛЕНО

Я додав відповідь до вищевказаного фрагмента, враховуючи нижченаведену відповідь .

Але все-таки наступні класи "ExecutorA" та "ExecutorB" мають лише кілька рядків коду. Чи доречно додавати їх як клас, ніж додавати його як метод? Будь ласка, проконсультуйте з цього приводу.



2
Чому ви перевантажуєте один сервлет 9 різними діями? Чому б просто не зіставити кожну дію на іншу сторінку, підкріплену іншим сервлетом? Таким чином, вибір дії здійснюється клієнтом, і ваш серверний код просто орієнтований на обслуговування запиту клієнта.
Можливо_Фактор

Відповіді:


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

І все ваше, якщо / інше у вашому doPostметоді стає чимось подібним

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-pattern-implemented
Том Тейлор

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, що так ...?
Том Тейлор

1
Так, я це виправив.
Вальфрат

1
І, взагалі, відповідь - «використовувати механізми ООП». Отже, тут вам слід переосмислити "ситуацію" та пов'язану з нею поведінку. Іншими словами, представляючи свою логіку абстрактним об'єктом, а потім маніпулюйте цим об'єктом замість його основних гайок і болтів.
mgoeminne

Крім того, природне розширення підходу, запропонованого @Walfrat, полягатиме у запропонуванні (абстрактної) фабрики, яка створює / повертає правильний ProcessAction залежно від заданого параметра String.
mgoeminne

@mgoeminne Це звук справа
Дж. Пічардо

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 . Нові команди не змінюють код картографа команд. Команди прості у виконанні. Їх можна просто додати до файлу проекту та властивостей. Команди повинні бути повторно введеними, і це робить її дуже ефективною.


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

Заводський метод - це те, що я виглядаю, якщо ви шукаєте масштабований і менш ремонтопридатний дизайн.

Заводський метод шаблону визначає інтерфейс для створення об'єкта, але нехай підклас вирішує, який клас для екземплярів. Заводський метод дозволяє класу відкладати екземпляри для підкласу.

 abstract class action {abstract doStuff(action)}

action1, action2 ........ actionN конкретна реалізація із застосуванням методу doStuff.

Просто зателефонуйте

    action.doStuff(actionN)

Тож у майбутньому, якщо буде впроваджено більше дій, потрібно просто додати конкретний клас.


typo abstarct -> конспект у першому рядку коду. Відредагуйте. Крім того, ви можете додати трохи більше коду, щоб вимити цей приклад, щоб показати більш прямо, як це відповідає на питання ОП?
Джей Елстон

0

З посиланням на @J. Відповідь Пічардо, я змінюю описаний вище фрагмент наступним чином

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

Чи ви не створюєте занадто багато класів, якщо занадто багато дій. Чи маємо кращу реалізацію.
Вайбхав Шарма
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.