Длинный список операторов if в Java


101

Извините, я не могу найти ответ на этот вопрос, я почти уверен, что кто-то уже поднимал его раньше.

Моя проблема в том, что я пишу некоторые системные библиотеки для запуска встроенных устройств. У меня есть команды, которые можно отправлять на эти устройства по радио. Это можно сделать только с помощью текста. внутри системных библиотек у меня есть поток, который обрабатывает команды, которые выглядят так

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

Проблема в том, что есть много команд, которые быстро выйдут из-под контроля. Ужасно смотреть, мучительно отлаживать и ошеломляюще понять через несколько месяцев.


16
Просто комментарий - я настоятельно рекомендую взять книгу шаблонов Gang of Four или, если вы новичок в шаблонах, книгу Head First Design Patterns in Java (которая довольно легко читается и является отличным введением в ряд общих шаблонов ). Оба являются ценными ресурсами, и оба не раз спасали мой бекон.
aperkins

2
Да, на самом деле они у меня были, но их нет :) Вот почему я был уверен, что делаю неправильно :) Но не смог найти правильного решения! Может быть, это займет хорошую позицию в Google
Стив

2
Здесь просто Command Pattern Monday!
Ник Вейс,

Ответы:


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 if на:

commandMap.get(value).exec();

РЕДАКТИРОВАТЬ

вы также можете добавить специальные команды, такие как UnknownCommandили NullCommand, но вам понадобится CommandMapкоманда, которая обрабатывает эти угловые случаи, чтобы минимизировать проверки клиентов.


1
... с соответствующей проверкой, что commandMap.get () не возвращает null :-)
Брайан Агнью

3
конечно, я опустил некоторый шаблонный код для простоты
dfa

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

2
это заставит реализовать все команды в перечислении ... что далеко от идеала. С интерфейсом вы также можете применить шаблон Decorator (например, 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 на самом деле не «подходят» к типу перечисления, что, как обычно, когда вам приходится идти на компромисс, является немного нечетким.


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

Если у вас больше, чем несколько команд, изучите использование шаблона Command, как указано в другом месте (хотя вы можете сохранить перечисление и встроить вызов в реализующий класс внутри перечисления вместо использования HashMap). См. Пример ответа Андреаса или Йенса на этот вопрос.


5
для каждой новой команды, которую вы добавляете, вам необходимо отредактировать переключатель: этот код не соответствует принципу открытия / закрытия
dfa

Зависит от того, мало ли команд или много, не так ли? Кроме того, в наши дни этот сайт настолько медленный, что для редактирования ответа требуется 5 попыток.
JeeBee,

это не оптимально, см. stackoverflow.com/questions/1199646/… о том, как сделать это более оптимальным.
Андреас Петерссон

Да, спасибо, что потратили время на реализацию того, что я написал в конце моего комментария - Java Enum as 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 as Key.


3

Даже если я считаю, что подход с использованием командных шаблонов больше ориентирован на лучшие практики и удобен в обслуживании в долгосрочной перспективе, вот вам один вариант:

org.apache.commons.beanutils.MethodUtils.invokeMethod (это, "doCommand" + значение, ноль);


2

Я обычно пытаюсь решить это таким образом:

public enum Command {

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

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

abstract void exec();
 }

у этого есть много преимуществ:

1) невозможно добавить перечисление без реализации exec. так что вы не пропустите А.

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

3) вы сэкономите любые потраченные впустую циклы процессора, просмотрев длинный список if или вычислив хэш-коды и выполнив поиск.

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


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

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

но вы можете написать программу для написания вашего кода. Все это очень систематично if (value = 'A') commandA (); иначе, если (........................ и т. д.


0

Я не уверен, есть ли у вас какое-либо совпадение между поведением ваших различных команд, но вы также можете взглянуть на шаблон Chain Of Responsibility , который может обеспечить большую гибкость, позволяя нескольким командам обрабатывать некоторые входные значения.


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.