EDIT:
As pointed out by Steve Evers and pdr, I am not correctly implementing the Memento pattern, my design is actually State pattern.
Menu Program
I built a console-based menu program with multiple levels that selects a particular test to run. Each level more precisely describes the operation. At any level you can type back to go back one level (memento).
Level 1: Server Type?
[1] Server A [2] Server B
Level 2: Server environment?
[1] test [2] production
Level 3: Test type?
[1] load [2] unit
Level 4: Data Collection?
[1] Legal docs [2] Corporate docs
Level 4.5 (optional): Load Test Type
[2] Multi TIF [2] Single PDF
Level 5: Command Type?
[1] Move [2] Copy [3] Remove [4] Custom
Level 6: Enter a keyword
[setup, cleanup, run]
Design
States
PROBLEM:
Right now the STATES enum is the determining factor as to what state is BACK and what state is NEXT yet it knows nothing about what the current memento state is.
Has anyone experienced a similar issue and found an effective way to handle mementos with optional state?
static enum STATES {
SERVER, ENVIRONMENT, TEST_TYPE, COLLECTION, COMMAND_TYPE, KEYWORD, FINISHED
}
Possible Solution (Not-flexible)
In reference to my code below, every case statement in the Menu class could check the state of currentMemo and then set the STATE (enum) accordingly to pass to the Builder. However, this doesn't seem flexible very flexible to change and I'm struggling to see an effective way refactor the design.
class Menu extends StateConscious {
private State state;
private Scanner reader;
private ServerUtils utility;
Menu() {
state = new State();
reader = new Scanner(System.in);
utility = new ServerUtils();
}
// Recurring menu logic
public void startPromptingLoop() {
List<State> states = new ArrayList<>();
states.add(new State());
boolean redoInput = false;
boolean userIsDone = false;
while (true) {
// get Memento from last loop
Memento currentMemento = states.get(states.size() - 1)
.saveMemento();
if (currentMemento == null)
currentMemento = new Memento.Builder(0).build();
if (!redoInput)
System.out.println(currentMemento.prompt);
redoInput = false;
// prepare Memento for next loop
Memento nextMemento = null;
STATES state = STATES.values()[states.size() - 1];
// get user input
String selection = reader.nextLine();
switch (selection) {
case "exit":
reader.close();
return; // only escape
case "quit":
nextMemento = new Memento.Builder(first(), currentMemento,
selection).build();
states.clear();
break;
case "back":
nextMemento = new Memento.Builder(previous(state),
currentMemento, selection).build();
if (states.size() <= 1) {
states.remove(0);
} else {
states.remove(states.size() - 1);
states.remove(states.size() - 1);
}
break;
case "1":
nextMemento = new Memento.Builder(next(state), currentMemento,
selection).build();
break;
case "2":
nextMemento = new Memento.Builder(next(state), currentMemento,
selection).build();
break;
case "3":
nextMemento = new Memento.Builder(next(state), currentMemento,
selection).build();
break;
case "4":
nextMemento = new Memento.Builder(next(state), currentMemento,
selection).build();
break;
default:
if (state.equals(STATES.CATEGORY)) {
String command = selection;
System.out.println("Executing " + command + " command on: "
+ currentMemento.type + " "
+ currentMemento.environment);
utility.executeCommand(currentMemento.nickname, command);
userIsDone = true;
states.clear();
nextMemento = new Memento.Builder(first(), currentMemento,
selection).build();
} else if (state.equals(STATES.KEYWORD)) {
nextMemento = new Memento.Builder(next(state),
currentMemento, selection).build();
states.clear();
nextMemento = new Memento.Builder(first(), currentMemento,
selection).build();
} else {
redoInput = true;
System.out.println("give it another try");
continue;
}
break;
}
if (userIsDone) {
// start the recurring menu over from the beginning
for (int i = 0; i < states.size(); i++) {
if (i != 0) {
states.remove(i); // remove all except first
}
}
reader = new Scanner(System.in);
this.state = new State();
userIsDone = false;
}
if (!redoInput) {
this.state.restoreMemento(nextMemento);
states.add(this.state);
}
}
}
}