Reputation: 4429
For a homework I code in Java(with which I'm a newbie), I bump into the problem of creating a console command list. The user will be confronted with a set of commands, among which s/he will choose his/her choice with a number. Something like this:
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
The first way I have done it is as follows(unnecessary code parts are truncated):
...
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;
}
}
Then, thinking that this is ugly, and for the sake of experimenting with Enums, I have tried the following:
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());
}
Actually, what I have in mind is using Enum's in switch with their ordinal values, but Java does not allow non-constant values in switch cases. What is the right way to solve this problem; the most elegant/scalable/flexible? Any constructive comments are highly appreciated!
Upvotes: 3
Views: 4280
Reputation: 54268
Using ordinals as identifiers is considered rather poor form, but perhaps you could use it like this?
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; }
}
and in your command handling class:
...
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();
}
But if you don't want to go the enum route there's nothing really stopping you from creating non-enum command objects. The upside of that is that they can implement, or can be configured to call the implementation of, the actual command:
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.
Upvotes: 1
Reputation: 70584
You could use Command.values()
, which contains the enums in ordinal order:
switch (Command.values[number]) {
case CREATE_A_TABLE:
...
}
A more elegant and maintainable way is to elimate the switch statement using polymorphism:
abstract class Command {
private String name;
protected Command(String name) {
this.name = name;
}
@Override public String toString() {
return name;
}
public abstract void execute();
}
and elsewhere:
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();
Advantages:
Upvotes: 2
Reputation: 9110
Well, it's a curious approach, but if you just want to look at the use of Enums, then "ordinal()" is not recommended.
Better to make an Enum with 2 members. Not ideal, but it might help ..
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; }
}
Now you can get an enum using the static 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) {
....
}
}
Upvotes: 1