Что такое хороший способ организовать команды для интерфейса командной строки? - PullRequest
0 голосов
/ 06 июля 2018

У меня проблемы с созданием ясного и лаконичного кода, который позволяет мне создавать различные команды для выполнения различных задач. Так, например, в симуляторе N-body, над которым я работаю, функциональность, которую я хочу, заключается в том, чтобы пользователь мог вводить команды, такие как tele pos [x] [y] [z] или tele celobj [celestial object name].

Для этого я делю входную строку на массив токенов в зависимости от того, где находятся пробелы. Затем я использую серию операторов переключения так, что первое слово (tele) обрабатывается в одном слое операторов переключения, а затем второе слово (pos или celobj) обрабатывается во втором слое операторов переключения , Затем следующие токены обрабатываются соответствующим образом. Во всех этих разных слоях я проверяю, что пользователь ввел правильное количество слов, чтобы избежать исключения вне диапазона.

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

Я включил свой исходный код на всякий случай, но я надеюсь, что мое описание было достаточно ясным.

public static void process(String cmd) {
    String tokenNotFound = "Token not recognized...";
    String notEnoughInfo = "Not enough info given. Please specify...";
    String unableToParse = "Unable to parse number...";

    String[] tokens = cmd.toLowerCase().split("\\s+");
    switch (tokens[0]) {
        case "close":
            run = false;
            break;
        case "toggle":
            if (tokens.length >= 2) {
                switch (tokens[1]) {
                    case "render":
                        render = !render;
                        System.out.println("Render setting set to " + render);
                        break;
                    case "physics":
                        updatePhysics = !updatePhysics;
                        System.out.println("Physics update setting set to " + updatePhysics);
                        break;
                    case "trails":
                        showTrails = !showTrails;
                        System.out.println("Show trails setting set to " + showTrails);
                        break;
                    case "constellations":
                        showConstellations = !showConstellations;
                        System.out.println("Show constellations setting set to " + showConstellations);
                        break;
                    default:
                        System.err.println(tokenNotFound);
                }
            } else
                System.err.println(notEnoughInfo);
            break;
        case "get":
            if (tokens.length >= 2) {
                switch (tokens[1]) {
                    case "fps":
                        System.out.println("FPS: " + realFPS);
                        break;
                    case "ups":
                        System.out.println("UPS: " + realUPS);
                        break;
                    case "cps":
                        System.out.println("CPS: " + realCPS);
                        break;
                    case "performance":
                        System.out.println("FPS: " + realFPS + " UPS: " + realUPS + " CPS: " + realCPS);
                        break;
                    case "time":
                        System.out.println(getTimestamp());
                        break;
                    case "celobj":
                        if (tokens.length >= 3) {
                            boolean objFound = false;
                            CelObj chosenObj = null;
                            for (CelObj celObj : physics.getCelObjs()) {
                                if (celObj.getName().toLowerCase().equals(tokens[2])) {
                                    objFound = true;
                                    chosenObj = celObj;
                                }
                            }

                            if (objFound) {
                                if (tokens.length >= 4) {
                                    switch (tokens[3]) {
                                        case "pos":
                                            Vec3d pos = chosenObj.getCelPos();
                                            System.out.println("POSITION: X= " + pos.x + " Y= " + pos.y + " Z= " + pos.z);
                                            break;
                                        case "vel":
                                            Vec3d vel = chosenObj.getCelVel();
                                            if (tokens.length >= 5 && tokens[4].equals("mag"))
                                                System.out.println("VELOCITY: V= " + vel.magnitude());
                                            else
                                                System.out.println("VELOCITY: X= " + vel.x + " Y= " + vel.y + " Z= " + vel.z);
                                            break;
                                        case "mass":
                                            System.out.println("MASS: M= " + chosenObj.getMass());
                                            break;
                                        case "radius":
                                            System.out.println("RADIUS: R= " + chosenObj.getRadius());
                                            break;
                                        default:
                                            System.err.println(notEnoughInfo);
                                    }
                                } else
                                    System.err.println(notEnoughInfo);
                            } else
                                System.err.println(tokenNotFound);
                        } else {
                            //Print list of celObjs
                            StringBuilder celObjNames = new StringBuilder("Celestial Objects: \n");
                            for (CelObj celObj : physics.getCelObjs()) {
                                celObjNames.append('\t').append(celObj.getName()).append('\n');
                            }
                            System.out.println(celObjNames.toString());
                        }
                        break;
                    default:
                        System.err.println(tokenNotFound);
                }
            } else
                System.err.println(notEnoughInfo);
            break;
        case "set":
            if (tokens.length >= 2) {
                switch (tokens[1]) {
                    case "cps":
                        if (tokens.length >= 3) {
                            try {
                                int newCPS = parseInt(tokens[2]);
                                realTime_to_simTime = newCPS * timeInc;
                                System.out.println("Target CPS set to " + newCPS);
                                System.out.println("The simulation time is " + realTime_to_simTime + " times the speed of real time");
                            } catch (Exception e) {
                                System.err.println(unableToParse);
                            }
                        } else
                            System.err.println(notEnoughInfo);
                        break;
                    case "scale":
                        if (tokens.length >= 3) {
                            try {
                                scale = parseFloat(tokens[2]);
                                System.out.println("Render object scale is now set to " + scale);
                            } catch (Exception e) {
                                System.err.println(unableToParse);
                            }
                        } else
                            System.err.println(notEnoughInfo);
                        break;
                    case "speed":
                        if (tokens.length >= 3) {
                            try {
                                speed = parseFloat(tokens[2]);
                                System.out.println("Speed is now set to " + speed);
                            } catch (Exception e) {
                                System.err.println(unableToParse);
                            }
                        } else
                            System.err.println(notEnoughInfo);
                        break;
                    case "record":
                        if (tokens.length >= 4) {
                            if (tokens[3].equals("period")) {
                                try {
                                    int newCPS = parseInt(tokens[2]);
                                    realTime_to_simTime = newCPS * timeInc;
                                    System.out.println("Target CPS set to " + newCPS);
                                    System.out.println("The recording period is now every " + realTime_to_simTime + " seconds");
                                } catch (Exception e) {
                                    System.err.println(unableToParse);
                                }
                            } else
                                System.err.println(tokenNotFound);

                        } else
                            System.err.println(notEnoughInfo);
                        break;
                    case "center":
                        if (tokens.length >= 3) {
                            boolean objFound = false;
                            CelObj chosenObj = null;
                            for (CelObj celObj : physics.getCelObjs()) {
                                if (celObj.getName().toLowerCase().equals(tokens[2])) {
                                    objFound = true;
                                    chosenObj = celObj;
                                }
                            }

                            if (objFound) {
                                centerCelObj = chosenObj;
                                System.out.println(chosenObj.getName() + " has been set as the center");
                            } else
                                System.err.println(tokenNotFound);
                        } else
                            System.err.println(notEnoughInfo);
                        break;
                    default:
                        System.err.println(tokenNotFound);
                }
            } else
                System.err.println(notEnoughInfo);
            break;
        case "create":
            //TODO:
            break;
        case "uncenter":
            centerCelObj = null;
            System.out.println("There is currently no center object");
            break;
        case "tele":
            if (tokens.length >= 2) {
                switch (tokens[1]) {
                    case "pos":
                        if (tokens.length >= 5) {
                            try {
                                double x = parseDouble(tokens[2]);
                                double y = parseDouble(tokens[3]);
                                double z = parseDouble(tokens[4]);

                                Vec3f cameraPos = new Vec3f((float) x, (float) y, (float) z);

                                //If camera is locked to an object, then translating the camera will only
                                //do so with respect to that planet
                                //Hence, the camera is translated back to world coordinates by translating it
                                //the negative of its locked celObj position vector
                                if (camera.getLockedCelObj() != null) {
                                    cameraPos.translate(
                                            new Vec3f(
                                                    camera.getLockedCelObj().getCelPos()
                                            ).negate()
                                    );
                                }

                                camera.setPosition(multiply(worldunit_per_meters, cameraPos));
                                System.out.println("The camera position has been set to X= " + x + " Y= " + y + " Z= " + z);
                            } catch (Exception e) {
                                System.err.println(unableToParse);
                            }
                        } else
                            System.err.println(notEnoughInfo);
                        break;
                    case "celobj":
                        if (tokens.length >= 3) {
                            boolean objFound = false;
                            CelObj chosenObj = null;
                            for (CelObj celObj : physics.getCelObjs()) {
                                if (celObj.getName().toLowerCase().equals(tokens[2])) {
                                    objFound = true;
                                    chosenObj = celObj;
                                }
                            }

                            if (objFound) {
                                Vec3f celObjPos = new Vec3f(chosenObj.getCelPos());
                                Vec3f cameraPos = add(celObjPos, new Vec3f(0, (float) chosenObj.getRadius() * 2, 0));

                                //If camera is locked to an object, then translating the camera will only
                                //do so with respect to that planet
                                //Hence, the camera is translated back to world coordinates by translating it
                                //the negative of its locked celObj position vector
                                if (camera.getLockedCelObj() != null) {
                                    cameraPos.translate(
                                            new Vec3f(
                                                    camera.getLockedCelObj().getCelPos()
                                            ).negate()
                                    );
                                }

                                //Make player 1 planet radius away from surface
                                camera.setPosition(multiply(worldunit_per_meters, cameraPos));
                                camera.setLookAt(multiply(worldunit_per_meters, celObjPos));

                                System.out.println("The camera position has been set to X= " + cameraPos.x + " Y= " + cameraPos.y + " Z= " + cameraPos.z);
                            } else
                                System.err.println(tokenNotFound);
                        } else
                            System.err.println(notEnoughInfo);
                        break;
                    default:
                        System.err.println(tokenNotFound);
                }
            } else
                System.err.println(notEnoughInfo);
            break;
        case "lock":
            if (tokens.length >= 2) {
                boolean objFound = false;
                CelObj chosenObj = null;
                for (CelObj celObj : physics.getCelObjs()) {
                    if (celObj.getName().toLowerCase().equals(tokens[1])) {
                        objFound = true;
                        chosenObj = celObj;
                    }
                }

                if (objFound) {
                    camera.setLockedCelObj(chosenObj);
                    camera.setPosition(new Vec3f(0, 0, 0));
                    System.out.println("The camera has been locked to " + chosenObj.getName());
                    System.out.println("Type 'unlock' to revert back to unlocked status");
                } else
                    System.err.println(tokenNotFound);
            } else
                System.err.println(notEnoughInfo);
            break;
        case "unlock":
            String celObjName = camera.getLockedCelObj().getName();
            //If camera is locked to an object, then translating the camera will only
            //do so with respect to that planet
            //Hence, the camera is translated back to world equivalent of where it is in
            //that celObj's space by translating it the celObj's position
            camera.setPosition(
                    add(
                            multiply(worldunit_per_meters,
                                    (new Vec3f(camera.getLockedCelObj().getCelPos()))),
                            camera.getPosition()
                    )
            );
            camera.setLockedCelObj(null);
            System.out.println("The camera has been unlocked from " + celObjName);
            Vec3f pos = camera.getPosition();
            System.out.println("The camera position has been set to X= " + pos.x + " Y= " + pos.y + " Z= " + pos.z);
            break;
        case "lookat":
            if (tokens.length >= 3) {
                switch (tokens[1]) {
                    case "celobj":
                        boolean objFound = false;
                        CelObj chosenObj = null;
                        for (CelObj celObj : physics.getCelObjs()) {
                            if (celObj.getName().toLowerCase().equals(tokens[2])) {
                                objFound = true;
                                chosenObj = celObj;
                            }
                        }

                        if (objFound) {
                            camera.setLookAt(new Vec3f(multiply(worldunit_per_meters, chosenObj.getCelPos())));
                            System.out.println("The camera is now looking at " + chosenObj.getName());
                        } else
                            System.err.println(tokenNotFound);
                        break;
                }
            } else
                System.err.println(notEnoughInfo);
            break;
        default:
            System.err.println(tokenNotFound);
    }
}

Ответы [ 2 ]

0 голосов
/ 06 июля 2018

рассмотрите возможность использования getopt

 Getopt g = new Getopt("testprog", argv, "ab:c::d");
 //
 int c;
 String arg;
 while ((c = g.getopt()) != -1)
   {
     switch(c)
       {
          case 'a':
          case 'd':
            System.out.print("You picked " + (char)c + "\n");
            break;
            //
          case 'b':
          case 'c':
            arg = g.getOptarg();
            System.out.print("You picked " + (char)c + 
                             " with an argument of " +
                             ((arg != null) ? arg : "null") + "\n");
            break;
            //
          case '?':
            break; // getopt() already printed an error
            //
          default:
            System.out.print("getopt() returned " + c + "\n");
       }
   }

связанные

0 голосов
/ 06 июля 2018

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

...