Skip to Content
Course content

294: The Memento Pattern

Click on the "Edit" button in the top corner of the screen to edit your slide content.

We've all been there: you're building a feature, and suddenly the stakeholder asks for an "Undo" button. At first glance, it seems trivial. You just need to save the state of the object before you change it, right? But as your objects grow in complexity, the way you implement that "save" can either keep your code clean or turn your architecture into a tangled mess of public getters and setters.

The temptation to just expose everything

The most intuitive—and usually wrong—way to handle this is to make the object's internal state public enough that some external "History Manager" can read it. Imagine we're building a RPG game where a GameCharacter has health, mana, and coordinates. You might be tempted to do something like this:

public class GameCharacter {
    private int health;
    private int mana;
    private int x, y;

    // Getters for everything so the SaveSystem can see them
    public int getHealth() { return health; }
    public int getMana() { return mana; }
    public int getX() { return x; }
    public int getY() { return y; }

    public void setHealth(int health) { this.health = health; }
    // ... other setters
}

In this naive approach, your SaveSystem class would simply call all those getters, store the values in a Map or a POJO, and then call the setters to restore them. It works. For about a week. Then you add a new attribute—maybe stamina or experiencePoints—and suddenly your SaveSystem is broken. You have to go back and manually update the save logic every single time the GameCharacter evolves.

Why public getters are a trap for state management

The problem here isn't just the maintenance headache; it's a fundamental violation of encapsulation. By exposing the internal state just to support an undo feature, you're telling the rest of your application that it's okay to reach inside the GameCharacter and mess with its guts. You've effectively made your private fields public, just with more keystrokes. I've seen this lead to bugs where other developers start using those "save-only" getters to implement game logic, creating a spiderweb of dependencies that makes the code impossible to refactor.

Hiding the state in a Memento

The Memento pattern solves this by shifting the responsibility of "remembering" back to the object that actually owns the data. Instead of the SaveSystem pulling data out, the GameCharacter pushes a snapshot of itself into a small, opaque object called a Memento.

The trick is to make the Memento's internal state inaccessible to everyone except the character who created it. In Java, we can do this beautifully with a private inner class:

public class GameCharacter {
    private int health;
    private int mana;

    // The Memento is a snapshot of the state
    public static class Memento {
        private final int health;
        private final int mana;

        private Memento(int health, int mana) {
            this.health = health;
            this.mana = mana;
        }

        private int getHealth() { return health; }
        private int getMana() { return mana; }
    }

    public Memento save() {
        return new Memento(health, mana);
    }

    public void restore(Memento memento) {
        this.health = memento.getHealth();
        this.mana = memento.getMana();
    }
}

Now, look at what happened to the SaveSystem. It no longer needs to know that GameCharacter has health or mana. It just holds a reference to a Memento object. It's essentially holding a sealed envelope; it knows the envelope contains the state, but it can't open it or change what's inside. It just hands the envelope back to the character when it's time to restore.

Trading memory for encapsulation

Is this approach perfect? No. There's a trade-off. You're now creating new objects every time you take a snapshot. If your state is massive—think thousands of entities in a game world—creating Mementos every few frames will spike your garbage collection and eat through your heap. In those cases, you might need to look at more complex delta-based saves (saving only what changed).

But for the vast majority of business logic, the trade-off is a no-brainer. You get a clean separation of concerns: the GameCharacter manages its own data, and the SaveSystem (the "Caretaker") simply manages the timeline of snapshots. You can add ten new fields to your character, and as long as you update the inner Memento class, your save/undo logic remains completely untouched.




📋 Practical Task

Implement a Snapshot System for a Text Editor Buffer

You are building a simplified text editor. Create a TextBuffer class that maintains a StringBuilder for the current text and an int for the cursor position. Implement the Memento pattern to allow the buffer to be restored to a previous state.

  • Create a private inner Memento class within TextBuffer that captures both the text content and the cursor position.
  • Implement a save() method that returns a Memento.
  • Implement a restore(Memento memento) method that reverts the buffer and cursor.
  • Create a History class (the Caretaker) that uses a Stack to store these mementos, providing push(Memento m) and pop() methods.

Test your implementation: Write a main method where you add text, save a snapshot, add more text, move the cursor, and then successfully restore the buffer to the exact state it was in at the time of the snapshot.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.