创建控制台命令列表 - 如何使用Java正确实现

3

作为一个Java编程的初学者,我在完成一项作业时遇到了创建控制台命令列表的问题。用户将面临一组命令,其中他/她将通过数字选择自己想要执行的命令。类似于这样:

Enter your choice:
0) Create a Table
1) List All Tables
2) Delete a Table
3) Insert a Record
4) List All Records
5) Delete a Record
6) Find a Record(by =)
7) Find a Record(by >)
8) Find a Record(by <)
9) Exit

我所采用的第一种方法如下(省略了不必要的代码部分):
...

outerLoop: while (true) {
    Scanner s = new Scanner(System.in);
    try {
        while (true) {
            System.out.println("Enter your choice:");
            displayChoiceList();
            int choice = s.nextInt();
            switch (choice) {
            case 1:
                processTableCreation();
                break;
            case 2:
                catalog.listAllTables();
                break;
            case 3:
                System.out.println("Enter the name of table:");
                String tableName = s.nextLine();
                catalog.deleteTable(tableName);
                break;
            case 4:
                processRecordInsertion();
                break;
            case 5:
                processListAllRecords();
                break;
            case 6:
                processDeleteRecord();
                break;
            case 7:
                processFindRecord(Constants.Operator.EQUAL);
                break;
            case 8:
                processFindRecord(Constants.Operator.SMALLER);
                break;
            case 9:
                processFindRecord(Constants.Operator.GREATER);
                break;
            case 10:
                break outerLoop;
            }
        }
    } catch (IllegalArgumentException e) {
        System.out.println("Error: " + e.getMessage());
    } catch (IOException e) {
        System.out.println(e.getMessage());
        return;
    }
}

...

private static void displayChoiceList() {
    String[] choices = new String[] { "Create Table", "List All Tables",
            "Delete a Table", "Insert a Record to a Table",
            "List all records", "Delete a record",
            "Find by Primary Key(=)", "Find by Primary Key(<)",
            "Find by Primary Key(>)", "Exit" };
    int id = 0;
    for (String choice : choices) {
        System.out.println((id + 1) + ") " + choice);
        ++id;
    }
}

然后,考虑到这很丑,为了尝试枚举类型,我尝试了以下操作:

private enum Command {
    CREATE_TABLE("Create a Table"),
    LIST_ALL_TABLES("List All Tables"),
    DELETE_TABLE("Delete a Table"),
    INSERT_RECORD("Insert a Record"),
    LIST_ALL_RECORDS("List All Records"),
    DELETE_RECORD("Delete a Record"),
    FIND_RECORD_EQ("Find a Record(by =)"),
    FIND_RECORD_GT("Find a Record(by >)"),
    FIND_RECORD_LT("Find a Record(by <)"),
    EXIT("Exit");

    private final String message;

    Command(String message) {
        this.message = message;
    }

    public String message() { return this.message; }
}

...

outerLoop: while (true) {
    Scanner s = new Scanner(System.in);
    try {
        while (true) {
            System.out.println("Enter your choice:");
            displayChoiceList();
            int choice = s.nextInt();

            if (choice == Command.CREATE_TABLE.ordinal())
                processTableCreation();
            else if (choice == Command.LIST_ALL_TABLES.ordinal())
                catalog.listAllTables();
            else if (choice == Command.DELETE_TABLE.ordinal()) {
                System.out.println("Enter the name of table:");
                String tableName = s.nextLine();
                catalog.deleteTable(tableName);                     
            }
            else if (choice == Command.INSERT_RECORD.ordinal())
                processRecordInsertion();
            else if (choice == Command.LIST_ALL_RECORDS.ordinal())
                processListAllRecords();
            else if (choice == Command.DELETE_RECORD.ordinal())
                processDeleteRecord();
            else if (choice == Command.FIND_RECORD_EQ.ordinal())
                processFindRecord(Constants.Operator.EQUAL);
            else if (choice == Command.FIND_RECORD_LT.ordinal())
                processFindRecord(Constants.Operator.SMALLER);
            else if (choice == Command.FIND_RECORD_GT.ordinal())
                processFindRecord(Constants.Operator.GREATER);
            else if (choice == Command.EXIT.ordinal())
                break outerLoop;
            else
                System.out.println("Invalid command number entered!");
        } 
    } catch (IllegalArgumentException e) {
        System.out.println("Error: " + e.getMessage());
    } catch (IOException e) {
        System.out.println(e.getMessage());
        return;
    }
}

...

private static void displayChoiceList() {
    for (Command c : Command.values())
        System.out.println(c.ordinal() + ") " + c.message());
}

实际上,我想使用Enum在switch中使用它们的序数值,但Java不允许在switch case中使用非常量值。如何正确解决这个问题;最优雅/可扩展/灵活的方式是什么?欢迎提出建设性意见!

4个回答

2
您可以使用Command.values(),其中包含按序数排序的枚举:
switch (Command.values[number]) {
case CREATE_A_TABLE:
...
}

更加优雅和可持续的方法是使用多态消除switch语句:
abstract class Command {
    private String name;

    protected Command(String name) {
        this.name = name;
    }

    @Override public String toString() {
        return name;
    }

    public abstract void execute();
}

以及其他地方:

Command[] commands = {
    new Command("Create a table") {
        @Override public void execute() {
            // code to create a table
        }
    },
    new Command("List all tables") {
        @Override public void execute() {
            // code to list all tables
        }
    }
};

for (int i = 0; i < commands.length; i++) {
    System.out.println(i + ":" + command);
}

int number = getInput();

commands[number].execute();

优点:

  • 代码更短,更清晰。
  • 编译器检查每个命令是否已实现(使用switch语句时,可能会忘记添加case语句,这种错误只会在运行时发生。虽然一个好的编译器会在枚举切换时发出警告,但是警告比编译错误更容易被忽略)。==> 在维护过程中更加健壮。

1

1
好的,我所询问的部分与作业的本质完全无关,我只是想学习;因此这不算作弊。然而,我希望在不使用任何其他库的情况下完成这项任务。 - kolistivra

1

使用序数作为标识符被认为是相当不好的形式,但也许你可以像这样使用它?

private enum Command {
    CREATE_TABLE("Create a Table"),
    ...
    EXIT("Exit");

    private static final Map<Integer, Command> fromOrdinal;

    static {
        fromOrdinal = new HashMap<Integer, Command>();
        for (Command c : values()) {
            fromOrdinal.put(c.ordinal(), c);
        }
    }

    public static Command fromId(int commandId) {
        return fromOrdinal.get(c);
    }

    private final String message;

    Command(String message) {
        this.message = message;
    }

    public String message() { return this.message; }
}

并且在您的命令处理类中:

    ...
    Map<Command, Runnable> actions = new HashMap<Command, Runnable>(); // Fill this map with commands and implementations of runnable that does what the command should.

    ...
    void run(int command) {
        Runnable action = actions.get(Command.fromId(command));
        if (action == null)
            throw new IllegalArgumentException("No such command");
        action.run();
    }

但是,如果您不想使用枚举路线,那么您完全可以创建非枚举命令对象。这样做的好处是它们可以实现或配置为调用实际命令的实现:

interface Command {
    char getInputChar();
    String getMessage();
    void run();
}

Next step is to create a Map<Character, Command> constructed from map.put(c.getInputChar(), c) for all commands you want to use, much like the enum example. To execute you can just execute the run() method of the command.

只是一个新手问题:什么是“static { fromOrdinal = ... }”部分? - kolistivra
这是一个静态初始化块,您可以使用它来计算静态值等。它在类被加载后的某个时间运行一次,在第一次访问该类之前运行。但不要做任何可能失败的事情,因为静态代码中的异常会导致奇怪且有时难以调试的错误。 - Henrik Gustafsson
这似乎比必要的要复杂得多:为什么要手动创建一个fromOrdinal映射,当每个枚举类已经提供了一个返回声明顺序中枚举的values()方法呢? - meriton

1

嗯,这是一种好奇的方法,但如果你只想查看枚举的使用,那么不建议使用"ordinal()"。

最好创建一个有两个成员的枚举。虽然不理想,但可能会有所帮助..

private enum Command {
        CREATE_TABLE(0,"Create a Table"),
        LIST_ALL_TABLES(1,"List All Tables"),
        DELETE_TABLE(2,"Delete a Table"),
        INSERT_RECORD(3,"Insert a Record"),
        LIST_ALL_RECORDS(4,"List All Records"),
        DELETE_RECORD(5,"Delete a Record"),
        FIND_RECORD_EQ(6,"Find a Record(by =)"),
        FIND_RECORD_GT(7,"Find a Record(by >)"),
        FIND_RECORD_LT(8,"Find a Record(by <)"),
        EXIT(9,"Exit");

        private final String message;
        private final int code;

        public static Command get(int code) {
            for(Command c : Command.values()) {
                if(code==c.code) {
                    return c;
                }
            }
            return null;
        }

        Command(int code, String message) {
            this.code= code;
            this.message = message;
        }
        public int getCode() { return this.code; }
        public String message() { return this.message; }
    }

现在你可以使用静态的Command.get(int)来获取枚举。
    private static void runCommand(int choice) {
        Command command = Command.get(choice);
        System.out.println("You Chose '"+command.message()+"'\n\n");
        switch(command) {
                ....
        }
    }

嗨,亨利克。非常相似,但两者都有缺点。我现在意识到我应该推荐使用 EnumSet 而不是... http://download.oracle.com/javase/1,5.0/docs/api/java/util/EnumSet.html - laher
这种方法的问题(除了它的优点之外)是,如果我想添加一个ID为1的新命令,修改整个列表对我来说不太好;我需要移动所有其他值... 我不想手动修改ID...也许摆脱第一个成员并简单地使用序数会有所帮助? - kolistivra
你说得对,那是个问题,@meriton的回答更好! - laher

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接