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

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

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

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

102
Steve 29 Июл 2009 в 15:40

15 ответов

Лучший ответ

Используя шаблон команды:

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, который обрабатывает эти угловые случаи, чтобы минимизировать проверки клиентов.

171
dfa 10 Дек 2009 в 19:00

Мое предложение было бы своего рода легкой комбинацией перечисления и объекта 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 на самом деле не «подходят» к типу перечисления, что, как обычно, когда вам приходится идти на компромисс, является немного нечетким.

12
jens 29 Июл 2009 в 12:09

Имейте перечисление команд:

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). См. Пример ответа Андреаса или Йенса на этот вопрос.

7
JeeBee 29 Июл 2009 в 12:16

Реализация интерфейса, просто и ясно продемонстрированного 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();
7
Blessed Geek 30 Июл 2009 в 02:42

Что ж, я предлагаю создать объекты команд и поместить их в хэш-карту, используя строку в качестве ключа.

5
keuleJ 29 Июл 2009 в 11:46

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

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

3
dfa 30 Июл 2009 в 07:51

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

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
Andreas Petersson 29 Июл 2009 в 12:08

Вам, вероятно, лучше всего использовать карту команд.

Но если у вас есть набор из них, чтобы справиться, вы в конечном итоге столкнетесь с множеством карт. Тогда стоит попробовать сделать это с помощью 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
Community 23 Май 2017 в 11:47

На мой взгляд, ответ @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) в качестве аргумента.

2
Marlon Bernardes 2 Июл 2014 в 22:17

Если у вас есть несколько составных операторов if, это шаблон для использования механизма правил . См., Например, JBOSS Drools.

1
Pierre 29 Июл 2009 в 12:01

Просто используйте HashMap, как описано здесь:

0
ars 29 Июл 2009 в 11:47

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

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

0
user147042 29 Июл 2009 в 12:00

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

0
tinyd 29 Июл 2009 в 13:30

Командный шаблон - это лучший способ. Вот один пример использования 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"
  }
0
Hoa Nguyen 11 Мар 2019 в 15:27

Если он много чего делает, то кода будет много, от этого никуда не деться. Просто упростите отслеживание, дайте переменным понятные имена, комментарии тоже могут помочь ...

-1
Mark 29 Июл 2009 в 11:45