Функциональный массив в Java?
Может быть, я много думаю в C, но я не вижу решения, как решить это должным образом в Java. Я получаю ответ от моего сервера, который отправляет такую строку:
command params <xml...>
Клиент получает эту строку и извлекает команду. Теперь я хотел бы вызвать функцию, которая знает, как обрабатывать команду. На стороне C решение очевидно. Я реализовал массив с именем команды и связанными с ней функциями, поэтому я могу просто перебрать массив и вызвать функцию.
Есть ли способ сделать это и на Java? Я не знаю, что я мог бы вызвать функцию, основанную на имени. Поэтому в настоящее время я вижу следующие варианты:
- Сделать серию
if(command.euqals(COMMAND)
- Для каждой команды я могу создать отдельный объект, который я могу хранить в массиве (очень грязный).
- Используйте отражение, чтобы я мог иметь карту с именами функций и имен команд.
Есть ли другие варианты?
Операторы if не являются лучшим IMO, но, по крайней мере, они допускают ошибки компилятора и проверку типов. Использование отражения, по крайней мере, более элегантно, потому что я могу зациклить и расширить его, но, конечно, это означает, что я могу видеть только ошибки времени выполнения, если неправильно ввел имена.
4 ответа
Твоя вторая идея идиоматична. Использовать Map<String, Runnable>
сохранить имена команд и соответствующий код, затем commands.get(commandName).run()
выполнить один.
Не бойтесь создавать классы! Это может сделать ваш код более многословным, но гораздо проще написать класс и больше никогда не беспокоиться об этом, чем делать то же самое с switch
или же if ... else if ...
, Если ваши команды станут более сложными, чем отдельные методы (возможно, toString()
, undo()
...), вы будете все более рады, что вы использовали полиморфизм вместо условных выражений.
Я решил эту проблему в прошлом, используя следующий подход, он может подойти в вашем случае:
Есть стандартный интерфейс:
public interface Executable {
public String getCommandName();
public void execute(String[] params, String xml);
}
и х количество реализаций:
public class SaveExecutable implements Executable {
private static final String COMMAND_NAME = "SAVE";
public String getCommandName() {
return COMMAND_NAME;
}
public void execute(String[] params, String xml) {
...
}
}
Затем сохраните реализации в HashMap для поиска:
HashMap<String, Executor> executors = new HashMap<>();
executors.put("SAVE", new SaveExecutable());
Тогда у вас может быть метод для обработки общих команд (проверка и усечение массива опущены):
public void handleCommand(String[] command) {
executors.get(command[0]).execute(command);
}
Если вы хотите иметь только один файл, вы можете сделать это с enum
,
Попробуйте что-то вроде этого:
public class Command {
public enum CommandName {
UNDO,
PRINT,
RUN
}
public static void execute(String command, String[] params, String xml) {
try {
CommandName cname = CommandName.valueOf(command);
switch (cname) {
case UNDO:
undo (params, xml);
break;
case PRINT:
//
break;
}
} catch (IllegalArgumentException iae) {
// Unknown command
}
}
public static void undo (String[] params, String xml) {
// ....
}
}
Запустите команду:
Command.execute(command, params, xml);
Мне недавно показали функциональную уловку, которая проста, хотя и весьма эффективна: "обернуть" функцию или набор функций другой функцией.
Ниже приведен пример кода, который я написал, чтобы показать этот трюк. Мой пример служит только для одной функции для одной команды, но ее можно легко расширить до набора функций, возвращаемых при необходимости (отсюда включение типа Guava Mutlimap, который можно использовать)... Я возвращаю Optional, когда команда не совпадает, она "безопасно" возвращает ноль; Contact и ContacUnit являются типами доменов, которые все расширяют тип Organization... это должно иметь смысл в приведенном ниже коде.
package com.xxx.component;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.xxx.domain.Contact;
import com.xxx.domain.ContactUnit;
import com.xxx.domain.Organization;
import java.util.Optional;
import java.util.function.Function;
/**
* This serves up the functions used for the domain
* to validate itself.
* Created by beezerbutt on 06/04/2017.
*/
public class MapSetDomainFunctionFactory {
public static final Function<String, Optional<Organization>> toContactFromCwid = s-> Optional.ofNullable(s).map(Contact::new);
public static final Function<String, Optional<Organization>> toContactUnitFromKey = s-> Optional.ofNullable(s).map(ContactUnit::new);
public static final Function<String, Function<String, Optional<Organization>>> commandToFunctions = command -> {
if (command.equalsIgnoreCase("toContactFromCwid")) {
return MapSetDomainFunctionFactory.toContactFromCwid;
} else {
return null;
}
};
}
}
Чтобы облегчить жизнь, я включил код класса домена:
/**
* Created by beezerbutt on 06/04/2017.
*/
public class Contact implements Organization {
}
public class ContactUnit implements Organization {
}
public interface Organization {
}
Ниже приведен снимок теста Спока, который я провел, чтобы доказать, что код работает: