0

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

Design

States

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);
            }
        }
    }
}
3
  • 3
    this is not really a Memento pattern at all, it's a State pattern. Which is probably why you're overthinking it.
    – pdr
    Commented Jun 21, 2013 at 17:01
  • 1
    As pdr said, this isn't memento. Right now it's one of the weaker implementations of the State pattern (IMO only; I prefer variations where states manage their transitions themselves). However, I think that what you're trying to get to is a Chain of Responsibility (en.wikipedia.org/wiki/Chain-of-responsibility_pattern). Commented Jun 21, 2013 at 20:20
  • Thanks for the clarification, apparently I wasn't implementing Memento correctly. Commented Jun 21, 2013 at 21:08

2 Answers 2

1

Here's how I have handled a similar problem, though I don't think my implementation strictly followed Memento. Note that I was programming in ActionScript, which has a pretty robust event system that the whole thing hinges on. My understanding that events in Java don't actually center around an Event Object, so you may not be able to do what I did. Even so, there might be some concepts that you could apply.

Basically, everything that happened in the program was the result of an event that triggered a Command. Each Command knew how to undo itself. This knowledge was encapsulated in subclasses of an UndoStep Class. The UndoStep would be stored in a doubly linked list, along with the event that triggered the Command.

When an "Undo" event was received, the Command for that would look at the current link in the chain and run the undo step, then walk back a link. If a "Redo" event was received, the Command would dispatch the original event, which resulted in exactly the same result again. I can't remember if this would walk forward on the chain (which would result in multiple redos) or simply add a new link at the end, which would discard all the other Undos.

1
  • +1 for chain w/ double-linked list idea. That was a good suggestion, I decided to go with more of an easy workaround that wouldn't require refactoring my design. Commented Jun 24, 2013 at 14:45
0

I decided to stick with the state* pattern I had already implemented and I came up with a pretty simple solution that didn't involve a lot of code restructuring. I added an OPTIONAL state to the STATES enum and gave the StateConscious an optionalFlag boolean. For the StateConscious.next() and StateConscious.previous() methods I would give the proper next or previous state based on whether optionalFlag was set.

There were additional code changes to support the additional menu prompt but it was easily added.

Thanks to Amy Blankenship, pdr, and Steve Evers for their insight!

*Clarification: After posting question I realized I'm using State pattern not Memento pattern

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.