Video games, electronic documents, and computers in general would not be very useful if we couldn't retrieve previous state; we would never be able to save our progress! In object oriented programming, a design pattern that is useful for maintaining previous application state during runtime is called the Memento Pattern.
Assumptions
- Basic programming knowledge
- Knowledge of basic <a href="https://www.devmaking.com/learn/design-patterns/design-pattern-modeling/" target="_blank" style="color:inherit;">Modeling Notation</a>
What is the Memento Pattern?
A Memento is a class that stores the previous state of an object in order to restore from that state at a later time.
<div style="width:100%; margin:auto;text-align:center;"><img src="https://www.devmaking.com/img/topics/designpatterns/MementoPattern_01.png" alt="memento pattern UML diagram" style="max-width:95%;"> </div>
Key components:
- Memento: class that stores a
state
object. - Caretaker: maintains a list of previous mementos.
- Originator: holds onto a Memento object and can have it's state restored.
Like the <a href="https://www.devmaking.com/learn/design-patterns/command-pattern/" target="_blank" style="color:inherit;">Command Pattern</a>, the memento pattern is useful for conducting undo
operations as it stores a list of previous states which can be restored at any time.
Conceptualization
To demonstrate the memento pattern in action, we'll be creating a simple game called dragon attack. In this game, a mighty warrior and fearsome dragon face off against each other in a fiery battle.
Let's start off by defining our Memento
class and the GameState
that it will contain. Our game state will be simple since it only needs to store two integers:
class Memento {
GameState state;
Memento (GameState state) {
this.state = state;
}
GameState getState() {
return state;
}
}
class GameState {
dragonHealth;
warriorHealth;
GameState(int dragonHealth, int warriorHealth) {
this.dragonHealth = dragonHealth;
this.warriorHealth = warriorHealth;
}
}
Our memento object will act as a "snapshot" of the game state at a particular point in time. Next, we need to define our Originator
that will maintain the current copy of the game state. Additionally, it should be able to save
a new memento object with a snapshot of the state:
class Originator {
GameState state;
void setState(GameState state) {
this.state = state;
}
GameState getState() {
return state;
}
Memento save() {
// Depending on your implementation, you may have to copy() state!
return new Memento(state);
}
void getStateFrom(Memento memento) {
this.state = memento.getState();
}
}
Next, we want to define our Caretaker
class that maintains a list of Memento
objects for each prior game state. This is fairly straightforward:
class Caretaker {
List<Memento> gameHistory = new List<>();
void addMemento(Memento memento) {
gameHistory.add(memento);
}
Memento undo() {
return gameHistory.pop();
}
}
As you can see, we have an additional method, undo
that simply returns the most recent memento saved to it.
Finally, we have our classes defined and are ready to create our game!
Client Code:
class DragonAttack {
static void main(String[] args) {
print("The Warrior approaches!");
// Create our originator and caretaker:
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
// Set the initial state of our originator:
originator.setState(new GameState(100, 100));
// Save the state:
caretaker.addMemento(originator.save());
// The warrior and dragon will take 3 turns each:
for(int i = 0; i < 3; i++) {
print("Turn " + (i + 1));
// Calculate damage:
int warriorDamage = Random.randInt(0,20);
int dragonDamage = Random.randInt(0,20);
// Calculate new health left:
int newWarriorHealth = originator.getState().warriorHealth - warriorDamage;
int newDragonHealth = originator.getState().dragonHealth - dragonDamage;
// Save the new game state:
originator.setState(new GameState(newWarriorHealth, newDragonHealth));
caretaker.addMemento(originator.save());
print("WarriorHealth: " + newWarriorHealth);
print("DragonHealth: " + newDragonHealth);
print("-----");
}
// Undo the final turn:
print("Undoing the last attack:");
originator.getStateFrom(caretaker.undo());
print("WarriorHealth: " + originator.getState().warriorHealth);
print("DragonHealth: " + originator.getState().dragonHealth);
}
}
> Challenge: implement this in your favorite language with user input!
Output:
The warrior approaches!
Turn 1
WarriorHealth: 94
DragonHealth: 89
-----
Turn 2
WarriorHealth: 80
DragonHealth: 74
-----
Turn 3
WarriorHealth: 68
DragonHealth: 60
-----
Undoing the last attack:
WarriorHealth: 80
DragonHealth: 74
Recommended Resources
- <a target="_blank" href="https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=devmaking-20&linkId=bc32087110669f75d93b216df79816f0" style="color:#fff;border-radius:3px;background-color:#888;padding:1px 5px">Head First Design Patterns: A Brain-Friendly Guide</a> : An excellent primer for learning design patterns in a pragmatic way.
- <a target="_blank" href="https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=devmaking-20&linkId=ae25d94c4ea49870eb3115e0e4d2de90" style="color:#fff;border-radius:3px;background-color:#888;padding:1px 5px">Design Patterns: Elements of Reusable Object-Oriented Software</a> : The reference guide made famous by Gang of Four, still widely used today.
- <a href="https://www.draw.io" target="_blank" style="color:#fff;border-radius:3px;background-color:#888;padding:1px 5px">Draw.io</a>: A free, open-source tool for designing diagrams with built-in support for UML diagrams to make your own!