Довгий список операторів if на Java


101

Вибачте, що не можу знайти запитання, що відповість на це, я майже впевнений, що хтось інший раніше його піднімав.

Моя проблема полягає в тому, що я записую деякі системні бібліотеки для запуску вбудованих пристроїв. У мене є команди, які можна надсилати на ці пристрої по радіопередачам. Це можна зробити лише текстом. всередині системних бібліотек у мене є потік, який обробляє команди, схожі на це

if (value.equals("A")) { doCommandA() }
else if (value.equals("B")) { doCommandB() } 
else if etc. 

Проблема полягає в тому, що є багато команд, які швидко скрутяться до чогось поза контролем. Жахливо виглядати назовні, болісно налагоджувати, а розум болісно зрозуміти через кілька місяців.


16
Лише коментар - я настійно рекомендую підібрати книгу зразків «Банда чотирьох», або, якщо ви не знайомі з візерунками, шаблони дизайну Head First Design in Java (це досить легко читати і чудово ознайомити з низкою поширених шаблонів ). Обидва цінні ресурси, і обидва врятували моє сало не раз.
aperkins

2
Так, я фактично володів ними, але вони відсутні ... Ось чому я був впевнений, що я робив, було не так :) Хоча не вдалося знайти правильне рішення! Можливо, це отримує гарну позицію в google
Steve

2
Тут просто шаблон команди в понеділок!
Нік Вейс

Відповіді:


171

використовуючи шаблон команди :

public interface Command {
     void exec();
}

public class CommandA() implements Command {

     void exec() {
          // ... 
     }
}

// etc etc

потім створити Map<String,Command>об'єкт і заповнити його Commandекземплярами:

commandMap.put("A", new CommandA());
commandMap.put("B", new CommandB());

тоді ви можете замінити свій if / else, якщо ланцюжок на:

commandMap.get(value).exec();

EDIT

Ви можете також додати спеціальні команди , такі як UnknownCommandабо NullCommand, але вам потрібно , CommandMapщо ручки цих кутові випадків для мінімізації клієнта перевірки.


1
... за допомогою відповідної перевірки, що commandMap.get () не повертає null :-)
Brian Agnew

3
Звичайно, я просто пропустив код котла для простоти
dfa

10
Замість HashMap ви можете використовувати перерахунок Java, який дає вам чітко визначений набір команд замість каламутної карти. У вас може бути геттер у перерахунку: Command getCommand (); або навіть реалізувати exec () як абстрактний метод в enum, який реалізує кожен екземпляр (enum як команда).
JeeBee

2
це змусить реалізувати всі команди в перерахунку ... що далеко не ідеально. За допомогою інтерфейсу ви можете застосувати також шаблон декоратора (наприклад, DebugCommandDecorator, TraceCommandDecorator), в простому інтерфейсі Java є набагато більше гнучкості
dfa

5
Гаразд, для невеликого і постійно зростаючого набору команд enum є життєздатним рішенням.
dfa

12

Моя пропозиція була б якоюсь легкою комбінацією enum та Command об'єкта. Це ідіома, рекомендована Джошуа Блохом у пункті 30 Ефективної Java.

public enum Command{
  A{public void doCommand(){
      // Implementation for A
    }
  },
  B{public void doCommand(){
      // Implementation for B
    }
  },
  C{public void doCommand(){
      // Implementation for C
    }
  };
  public abstract void doCommand();
}

Звичайно, ви можете передати параметри doCommand або мати типи повернення.

Це рішення може бути не дуже підходящим, якщо реалізація doCommand насправді не «підходить» до типу enum, що - як зазвичай, коли потрібно робити компроміс - трохи нечітке.


7

Майте перелік команд:

public enum Commands { A, B, C; }
...

Command command = Commands.valueOf(value);

switch (command) {
    case A: doCommandA(); break;
    case B: doCommandB(); break;
    case C: doCommandC(); break;
}

Якщо у вас є більше декількох команд, розгляньте, використовуючи шаблон команди, як відповіли в іншому місці (хоча ви можете зберегти перерахунок і вбудувати виклик до класу реалізації в enum, замість того, щоб використовувати HashMap). Для прикладу див. Відповідь Андреаса чи джинса на це запитання.


5
для кожної нової команди, яку ви додали, вам потрібно відредагувати перемикач: цей код не відповідає принципу відкритого / закритого
dfa

Залежить від того, чи мало команд, чи багато, чи не так? Також цей сайт настільки страшенно повільний, що сьогодні потрібно 5 спроб відредагувати відповідь.
JeeBee

це не оптимально, див. stackoverflow.com/questions/1199646/… про те, як зробити це більш оптимально.
Андреас Петерсон

Так, дякую, що витратили час на реалізацію того, що я написав внизу свого коментаря - Java Enum як Command Pattern. Якби я міг редагувати свій пост, я б зазначив про це, але цей сайт вмирає.
JeeBee

Я думаю, що ці запитання кричать за заявою Switch!
Майкл Браун

7

Реалізація інтерфейсу, продемонстрована просто та просто dfa, є чіткою та елегантною (і "офіційно" підтримується). Для чого призначена концепція інтерфейсу.

У C # ми могли б використовувати делегатів для програмістів, які люблять використовувати покажчики функтону в c, але методика DFA - це спосіб використання.

Ви також можете мати масив

Command[] commands =
{
  new CommandA(), new CommandB(), new CommandC(), ...
}

Тоді ви могли виконати команду за індексом

commands[7].exec();

Плагіат із DFA, але замість інтерфейсу має абстрактний базовий клас. Зауважте cmdKey, який буде використаний пізніше. З досвіду я усвідомлюю, що часто у розпорядження обладнання є також підкоманди.

abstract public class Command()
{
  abstract public byte exec(String subCmd);
  public String cmdKey;
  public String subCmd;
}

Створіть свої команди таким чином,

public class CommandA
extends Command
{
  public CommandA(String subCmd)
  {
    this.cmdKey = "A";
    this.subCmd = subCmd;
  }

  public byte exec()
  {
    sendWhatever(...);
    byte status = receiveWhatever(...);
    return status;
  }
}

Потім можна розширити загальний HashMap або HashTable, надавши функцію всмоктування пари ключ-значення:

public class CommandHash<String, Command>
extends HashMap<String, Command>
(
  public CommandHash<String, Command>(Command[] commands)
  {
    this.commandSucker(Command[] commands);
  }
  public commandSucker(Command[] commands)
  {
    for(Command cmd : commands)
    {
      this.put(cmd.cmdKey, cmd);
    }
  }
}

Потім побудуйте свій командний магазин:

CommandHash commands =
  new CommandHash(
  {
    new CommandA("asdf"),
    new CommandA("qwerty"),
    new CommandB(null),
    new CommandC("hello dolly"),
    ...
  });

Тепер ви могли об'єктивно надсилати елементи керування

commands.get("A").exec();
commands.get(condition).exec();

+1 за згадування делегатів на всякий випадок, якщо будь-які люди .NET побачать це питання і з розуму зійдуться за методами інтерфейсу. Але вони насправді не порівнянні з функціональними покажчиками. Вони ближче до підтримуваної мовою версії шаблону команд.
Даніель Ервікер

5

Я пропоную створити об'єкти команд і помістити їх у хешмак, використовуючи String як ключ.


3

Навіть якщо я вважаю, що підхід до командного шаблону більше спрямований на найкращі втілення та підтримання в довгостроковій перспективі, ось для вас один варіант лайнера:

org.apache.commons.beanutils.MethodUtils.invokeMethod (це, "doCommand" + значення, null);


2

Я зазвичай намагаюся вирішити це таким чином:

public enum Command {

A {void exec() {
     doCommandA();
}},

B {void exec() {
    doCommandB();
}};

abstract void exec();
 }

це має багато переваг:

1) не можна додати enum без реалізації exec. так що ви не пропустите А.

2) вам навіть не доведеться додавати його до будь-якої командної карти, тому немає кодового коду для створення карти. просто абстрактний метод та його реалізація. (який, можливо, також є котлоагрегатом, але він не стане коротшим.)

3) ви збережете будь-які витрачені цикли процесора, переглянувши довгий список елементів if чи обчисливши хеш-коди та зробивши пошук.

редагувати: якщо у вас немає переписок, а рядків як джерела, просто використовуйте Command.valueOf(mystr).exec()для виклику методу exec. зауважте, що ви повинні використовувати загальнодоступний модифікатор у програмі execif, яку ви хочете викликати з іншого пакету.


2

Вам, мабуть, найкраще використовувати карту команд.

Але чи є у вас такий набір, щоб обробити, ви закінчите навантажувати навантаженнями Карт. Тоді варто поглянути на це з Enums.

Ви можете це зробити за допомогою Enum, не використовуючи комутатори (напевно, вам не потрібні геттери в прикладі), якщо ви додасте метод до Enum, щоб вирішити "значення". Тоді ви можете просто зробити:

Оновлення: додана статична карта, щоб уникнути ітерації під час кожного дзвінка. Безсоромно щипався від цієї відповіді .

Commands.getCommand(value).exec();

public interface Command {
    void exec();
}

public enum Commands {
    A("foo", new Command(){public void exec(){
        System.out.println(A.getValue());
    }}),
    B("bar", new Command(){public void exec(){
        System.out.println(B.getValue());
    }}),
    C("barry", new Command(){public void exec(){
        System.out.println(C.getValue());
    }});

    private String value;
    private Command command;
    private static Map<String, Commands> commandsMap;

    static {
        commandsMap = new HashMap<String, Commands>();
        for (Commands c : Commands.values()) {
            commandsMap.put(c.getValue(), c);    
        }
    }

    Commands(String value, Command command) {
        this.value= value;
        this.command = command;
    }

    public String getValue() {
        return value;
    }

    public Command getCommand() {
        return command;
    }

    public static Command getCommand(String value) {
        if(!commandsMap.containsKey(value)) {
            throw new RuntimeException("value not found:" + value);
        }
        return commandsMap.get(value).getCommand();
    }
}

2

На мою думку, відповідь, надана @dfa - це найкраще рішення.

Я просто надаю кілька фрагментів на випадок, якщо ви використовуєте Java 8 і хочете використовувати Lambdas!

Команда без параметрів:

Map<String, Command> commands = new HashMap<String, Command>();
commands.put("A", () -> System.out.println("COMMAND A"));
commands.put("B", () -> System.out.println("COMMAND B"));
commands.put("C", () -> System.out.println("COMMAND C"));
commands.get(value).exec();

(ви можете використовувати Runnable замість Command, але я не вважаю це семантично правильним):

Команда з одним параметром:

Якщо ви очікуєте параметр, який ви можете використовувати java.util.function.Consumer:

Map<String, Consumer<Object>> commands = new HashMap<String, Consumer<Object>>();
commands.put("A", myObj::doSomethingA);
commands.put("B", myObj::doSomethingB);
commands.put("C", myObj::doSomethingC);
commands.get(value).accept(param);

У наведеному вище прикладі doSomethingX- це метод, присутній у myObjкласі класу, який приймає будь-який Об'єкт (названий paramу цьому прикладі) як аргумент.


1

якщо у вас є кілька імбризованих висловлювань 'if', то це схема використання механізму правил . Дивіться, наприклад, JBOSS Drools .



0

якби можна було мати масив процедур (як ви називали команди), які були б корисні ..

але ви можете написати програму для написання свого коду. Це все дуже систематично, якщо (value = 'A') commandA (); інакше, якщо (........................ тощо


0

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


0

Командний шаблон - це шлях. Ось один приклад використання Java 8:

1. Визначте інтерфейс:

public interface ExtensionHandler {
  boolean isMatched(String fileName);
  String handle(String fileName);
}

2. Реалізуйте інтерфейс з кожним розширенням:

public class PdfHandler implements ExtensionHandler {
  @Override
  public boolean isMatched(String fileName) {
    return fileName.endsWith(".pdf");
  }

  @Override
  public String handle(String fileName) {
    return "application/pdf";
  }
}

і

public class TxtHandler implements ExtensionHandler {
  @Override public boolean isMatched(String fileName) {
    return fileName.endsWith(".txt");
  }

  @Override public String handle(String fileName) {
    return "txt/plain";
  }
}

і так далі .....

3. Визначте Клієнта:

public class MimeTypeGetter {
  private List<ExtensionHandler> extensionHandlers;
  private ExtensionHandler plainTextHandler;

  public MimeTypeGetter() {
    extensionHandlers = new ArrayList<>();

    extensionHandlers.add(new PdfHandler());
    extensionHandlers.add(new DocHandler());
    extensionHandlers.add(new XlsHandler());

    // and so on

    plainTextHandler = new PlainTextHandler();
    extensionHandlers.add(plainTextHandler);
  }

  public String getMimeType(String fileExtension) {
    return extensionHandlers.stream()
      .filter(handler -> handler.isMatched(fileExtension))
      .findFirst()
      .orElse(plainTextHandler)
      .handle(fileExtension);
  }
}

4. І це результат вибірки:

  public static void main(String[] args) {
    MimeTypeGetter mimeTypeGetter = new MimeTypeGetter();

    System.out.println(mimeTypeGetter.getMimeType("test.pdf")); // application/pdf
    System.out.println(mimeTypeGetter.getMimeType("hello.txt")); // txt/plain
    System.out.println(mimeTypeGetter.getMimeType("my presentation.ppt")); // "application/vnd.ms-powerpoint"
  }

-1

Якщо це робить багато речей, тоді буде багато коду, ви не можете дійсно від цього піти. Просто полегшіть їх дотримання, дайте змінні дуже значущі імена, коментарі також можуть допомогти ...

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