Ваши инстинкты верны. Код, который у вас есть, может быть действительно полезен, если его как-то разбить на более мелкие части. Отличный способ сделать это - сделать его более управляемым данными. Одним из способов кодирования длинного списка команд является оператор switch, но проблема в том, что оператор увеличивается и длиннее, чем больше у вас команд. Управляемый данными подход обрабатывает имена команд и код, стоящий за ними, как данные и отделяет список команд от кода, который анализирует и выполняет команды.
Давайте начнем с простого интерфейса, представляющего обработчик команд. Это функция, которая получает аргументы команды, а затем делает все, что делает команда.
public interface CommandHandler {
public void handle(List<String> arguments);
}
Тогда давайте сделаем функцию process()
управляемой данными. А пока давайте разберемся с первыми двумя командами «закрыть» и «переключить». Мы начнем с простого, посмотрим, имеет ли идея смысл, а затем уточним реализацию, как только мы узнаем на высоком уровне, что мы хотим сделать.
Мы создадим карту имен команд для их обработчиков. Это даст нам компактный список команд с кодом позади каждой команды, разделенной на отдельные функции обратного вызова. В случае, если вы не знакомы с этим, Commands::close
является ссылкой на метод. Он дает нам объект CommandHandler
, который вызывает метод Commands.close()
, который мы определим позже.
public static void process(String input) {
Map<String, CommandHandler> commands = new HashMap<>();
commands.put("close", Commands::close);
commands.put("toggle", Commands::toggle);
List<String> tokens = Arrays.asList(input.toLowerCase().split("\\s+"));
process(tokens, commands);
}
Это выглядит довольно хорошо. Это коротко и сладко. Он разбивает входную строку на токены, но это все, что он делает. Остальное откладывается до второго process()
метода. Давайте напишем это сейчас:
public static void process(List<String> tokens, Map<String, CommandHandler> commands) {
String command = tokens.get(0);
List<String> arguments = tokens.subList(1, tokens.size());
CommandHandler handler = commands.get(command);
if (handler != null) {
handler.handle(arguments)
}
}
Это ядро логики синтаксического анализа команд. Он ищет команду на карте и выполняет соответствующий обработчик, если находит его. Что приятно, этот метод ничего не знает ни о каких конкретных командах. Это все очень общее.
Он также настроен на поддержку подкоманд. Заметьте, как он принимает список токенов? И как это сохраняет аргументы в отдельном подсписке? Это означает, что его можно вызывать не только для команд верхнего уровня, но и для подкоманд, таких как «render».
Последняя часть головоломки определяет каждого из командных обработчиков. Я бросил их в их собственный класс, но ты не обязан это делать. Все это могут быть методы в вашем исходном классе (я просто не знаю, как вы назвали это все).
public class Commands {
public static void close(List<String> arguments) {
run = false;
}
public static void toggle(List<String> arguments) {
if (arguments.length == 0) {
System.err.println(notEnoughInfo);
return;
}
Map<String, CommandHandler> subCommands = new HashMap<>();
subCommands.put("render", arguments -> {
render = !render;
System.out.println("Render setting set to " + render);
});
subCommands.put("physics", arguments -> {
updatePhysics = !updatePhysics;
System.out.println("Physics update setting set to " + updatePhysics);
});
subCommands.put("trails", arguments -> {
showTrails = !showTrails;
System.out.println("Show trails setting set to " + showTrails);
});
subCommands.put("constellations", arguments -> {
showConstellations = !showConstellations;
System.out.println("Show constellations setting set to " + showConstellations);
});
process(arguments, subCommands);
}
}
toggle()
показывает разбор подкоманды. Так же, как и верхний код, он создает карту подкоманд и регистрирует их имена и обработчики. И так же, как наверху, он вызывает ту же функцию process()
, что и раньше.
На этот раз, поскольку все обработчики очень просты, нет необходимости разбивать их на отдельные именованные функции. Мы можем использовать анонимные лямбда-выражения для регистрации встроенных обработчиков. Как и Commands::close
ранее, arguments -> { code }
создает CommandHandler
inline.