Advanced Java Design Patterns: Memento and State Patterns
In the previous article, we explored the Strategy and Template patterns within behavioral design patterns. This article will delve into two additional behavioral patterns: the Memento Pattern and the State Pattern.
Memento Pattern
Overview
The Memento Pattern is used to capture and externalize an object's internal state without violating encapsulation, sothat the object can be restored to this state later. This pattern falls under the category of behavioral patterns.
Its primary purpose is to preserve an object's state in a way that allows it to be restored at a later time, while maintaining encapsulation.
Common applications include document saving (like in text editors), game saving systems, system backups, and undo mechanisms such as Ctrl+Z.
Components
The Memento Pattern consists of three key components:
- Memento: Stores the internal state of the originator.
- Originator: Creates and restores its state using mementos.
- Caretaker: Responsible for storing mementos but cannot inspect their contents.
Example Implementation
Let's consider a gaming scenario where players can save and load their progress.
First, define the Memento class that holds game state:
class GameState {
private int level;
private int health;
public GameState(int level, int health) {
this.level = level;
this.health = health;
}
public int getLevel() { return level; }
public void setLevel(int level) { this.level = level; }
public int getHealth() { return health; }
public void setHealth(int health) { this.health = health; }
}
Next, implement the Originator (the player):
class Player {
private int level;
private int health;
public Player(int level, int health) {
this.level = level;
this.health = health;
}
public GameState saveState() {
return new GameState(level, health);
}
public void restoreState(GameState state) {
this.level = state.getLevel();
this.health = state.getHealth();
}
public void displayStatus() {
System.out.println("Player Status - Level: " + level + ", Health: " + health);
}
public void levelUp() {
this.level++;
this.health += 10;
System.out.println("Player leveled up! Level increased by 1, Health increased by 10.");
}
public boolean canChallengeBoss() {
return this.level > 2 && this.health > 100;
}
}
Finally, create the Caretaker (save manager):
class SaveManager {
private GameState savedState;
public GameState getSavedState() {
return savedState;
}
public void setSavedState(GameState state) {
this.savedState = state;
}
}
Testing implementation:
public static void main(String[] args) {
int initialLevel = 1;
int initialHealth = 100;
Player player = new Player(initialLevel, initialHealth);
System.out.println("Player enters game!");
player.displayStatus();
player.levelUp();
player.displayStatus();
SaveManager saveManager = new SaveManager();
System.out.println("Saving game state...");
saveManager.setSavedState(player.saveState());
System.out.println("Game saved successfully!");
System.out.println("Attempting to challenge boss...");
if (player.canChallengeBoss()) {
System.out.println("Boss defeated!");
return;
}
System.out.println("Boss defeat! Loading saved game...");
player.restoreState(saveManager.getSavedState());
System.out.println("Game loaded successfully!");
player.levelUp();
player.displayStatus();
System.out.println("Attempting to challenge boss again...");
if (player.canChallengeBoss()) {
System.out.println("Boss defeated!");
}
}
Advantages
- Provides a mechanism to restore object states
- Encapsulates state information
Disadvantages
- Resource intensive
- Requires knowledge of all possible states
Use Cases
- Scenarios requiring state persistence and restoration
State Pattern
Overview
The State Pattern is a behavioral design pattern that enables an object to alter its behavior when its internal state changes. It's particularly useful when complex conditional logic governs an object's behavior.
This pattern moves state-dependent logic out of the main class into separate state classes, simplifying the main class's responsibilities.
Components
- Context: Maintains a reference to the current state object
- State Interface: Defines methods for handling state-specific behavior
- Concrete States: Implement the state interface with specific behaviors
Example Implementation
Consider a music player with play/pause functionality:
interface PlayerState {
void press();
}
class PlayingState implements PlayerState {
@Override
public void press() {
System.out.println("Music playing...");
}
}
class PausedState implements PlayerState {
@Override
public void press() {
System.out.println("Music paused.");
}
}
class MusicPlayer {
private PlayerState currentState;
private int toggleCount = 0;
public MusicPlayer(PlayerState initialState) {
this.currentState = initialState;
}
public void pressButton() {
if ((toggleCount & 1) == 0) {
currentState = new PlayingState();
} else {
currentState = new PausedState();
}
currentState.press();
toggleCount++;
}
}
Testing the implementation:
public static void main(String[] args) {
MusicPlayer player = new MusicPlayer(new PlayingState());
player.pressButton(); // Play
player.pressButton(); // Pause
player.pressButton(); // Play
}
Advantages
- Easy to extend with new states
- Improves code organization
Disadvantages
- Increases number of classes
- Complexity in state transitions
Use Cases
- Objects whose behavior changes based on internal state
- Complex conditional logic replacement
Comparison with Strategy Pattern
While both patterns appear similar, they differ in key aspects:
- State Pattern: States are mutually exclusive and cannot be interchanged
- Strategy Pattern: Strategies are interchangeable
- State changes are managed internally
- Strategy changes are typically initiated by the client
Conclusion
Both patterns provide elegant solutions for managing object behavior and state. The Memento Pattern excels in scenarios requiring state persistence, while the State Pattern shines in situations where object behavior varies based on its internal state.