The Memento Pattern is a behavioral design pattern that allows capturing and externalizing an object's internal state without violating encapsulation, so the object can be restored to this state later.
1. Basic Memento Pattern Implementation
Core Structure
import java.util.ArrayList;
import java.util.List;
// Memento - stores the state of the Originator
class TextMemento {
private final String state;
private final long timestamp;
public TextMemento(String state) {
this.state = state;
this.timestamp = System.currentTimeMillis();
}
public String getState() {
return state;
}
public long getTimestamp() {
return timestamp;
}
@Override
public String toString() {
return String.format("Memento[time=%tT, chars=%d]",
timestamp, state.length());
}
}
// Originator - creates and uses mementos
class TextEditor {
private String content;
private String font;
private int fontSize;
private String color;
public TextEditor() {
this.content = "";
this.font = "Arial";
this.fontSize = 12;
this.color = "Black";
}
// Write text
public void write(String text) {
this.content += text;
System.out.println("Content: " + content);
}
// Delete last character
public void delete() {
if (!content.isEmpty()) {
content = content.substring(0, content.length() - 1);
System.out.println("Content after delete: " + content);
}
}
// Formatting methods
public void setFont(String font) {
this.font = font;
System.out.println("Font set to: " + font);
}
public void setFontSize(int fontSize) {
this.fontSize = fontSize;
System.out.println("Font size set to: " + fontSize);
}
public void setColor(String color) {
this.color = color;
System.out.println("Color set to: " + color);
}
// Create memento
public TextMemento save() {
System.out.println("Saving state...");
return new TextMemento(content);
}
// Restore from memento
public void restore(TextMemento memento) {
this.content = memento.getState();
System.out.println("Restored state from: " + memento);
System.out.println("Current content: " + content);
}
// Get current state
public void printState() {
System.out.printf("Current State: '%s' [Font: %s, Size: %d, Color: %s]%n",
content, font, fontSize, color);
}
public String getContent() {
return content;
}
}
// Caretaker - manages mementos
class History {
private final List<TextMemento> mementos = new ArrayList<>();
private int currentState = -1;
public void saveState(TextMemento memento) {
// Remove any future states if we're not at the end
if (currentState < mementos.size() - 1) {
mementos.subList(currentState + 1, mementos.size()).clear();
}
mementos.add(memento);
currentState = mementos.size() - 1;
System.out.println("State saved. Total states: " + mementos.size());
}
public TextMemento undo() {
if (currentState <= 0) {
System.out.println("Cannot undo - no previous state");
return null;
}
currentState--;
TextMemento memento = mementos.get(currentState);
System.out.println("Undo to: " + memento);
return memento;
}
public TextMemento redo() {
if (currentState >= mementos.size() - 1) {
System.out.println("Cannot redo - no future state");
return null;
}
currentState++;
TextMemento memento = mementos.get(currentState);
System.out.println("Redo to: " + memento);
return memento;
}
public void showHistory() {
System.out.println("\n=== History ===");
for (int i = 0; i < mementos.size(); i++) {
String marker = (i == currentState) ? "-> " : " ";
System.out.println(marker + mementos.get(i));
}
System.out.println();
}
public void clear() {
mementos.clear();
currentState = -1;
System.out.println("History cleared");
}
}
// Client code
public class MementoPatternDemo {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
History history = new History();
// Edit text with saves
editor.write("Hello");
history.saveState(editor.save());
editor.write(" World");
history.saveState(editor.save());
editor.write("!");
history.saveState(editor.save());
editor.printState();
history.showHistory();
// Undo operations
System.out.println("\n=== Undo Operations ===");
editor.restore(history.undo());
editor.restore(history.undo());
history.showHistory();
// Redo operation
System.out.println("=== Redo Operation ===");
editor.restore(history.redo());
// Continue editing
System.out.println("\n=== Continue Editing ===");
editor.write(" Java");
history.saveState(editor.save());
editor.write(" Programming");
history.saveState(editor.save());
history.showHistory();
editor.printState();
}
}
2. Real-World Example: Game Save System
Game State Management with Memento
import java.io.*;
import java.util.*;
// Game State Memento
class GameSave implements Serializable {
private final String playerName;
private final int level;
private final int score;
private final int health;
private final List<String> inventory;
private final Map<String, Object> gameState;
private final long timestamp;
private final String saveName;
public GameSave(String playerName, int level, int score, int health,
List<String> inventory, Map<String, Object> gameState, String saveName) {
this.playerName = playerName;
this.level = level;
this.score = score;
this.health = health;
this.inventory = new ArrayList<>(inventory);
this.gameState = new HashMap<>(gameState);
this.timestamp = System.currentTimeMillis();
this.saveName = saveName;
}
// Getters
public String getPlayerName() { return playerName; }
public int getLevel() { return level; }
public int getScore() { return score; }
public int getHealth() { return health; }
public List<String> getInventory() { return new ArrayList<>(inventory); }
public Map<String, Object> getGameState() { return new HashMap<>(gameState); }
public long getTimestamp() { return timestamp; }
public String getSaveName() { return saveName; }
@Override
public String toString() {
return String.format("Save: %s [Player: %s, Level: %d, Score: %d, Health: %d, Items: %d]",
saveName, playerName, level, score, health, inventory.size());
}
}
// Originator - Game Character
class GameCharacter {
private String playerName;
private int level;
private int score;
private int health;
private List<String> inventory;
private Map<String, Object> gameState;
public GameCharacter(String playerName) {
this.playerName = playerName;
this.level = 1;
this.score = 0;
this.health = 100;
this.inventory = new ArrayList<>();
this.gameState = new HashMap<>();
// Initial items
inventory.add("Sword");
inventory.add("Potion");
}
// Game actions
public void levelUp() {
level++;
score += 100;
health = 100; // Full health on level up
System.out.println(playerName + " leveled up to level " + level + "!");
}
public void takeDamage(int damage) {
health = Math.max(0, health - damage);
System.out.println(playerName + " took " + damage + " damage. Health: " + health);
if (health == 0) {
System.out.println(playerName + " has been defeated!");
}
}
public void heal(int amount) {
health = Math.min(100, health + amount);
System.out.println(playerName + " healed " + amount + " points. Health: " + health);
}
public void addScore(int points) {
score += points;
System.out.println("+" + points + " points! Total score: " + score);
}
public void addItem(String item) {
inventory.add(item);
System.out.println("Added item: " + item);
}
public void useItem(String item) {
if (inventory.remove(item)) {
System.out.println("Used item: " + item);
// Item effects
switch (item.toLowerCase()) {
case "potion":
heal(30);
break;
case "super potion":
heal(60);
break;
case "mega potion":
health = 100;
System.out.println("Fully healed!");
break;
}
} else {
System.out.println("Item not found: " + item);
}
}
public void setGameState(String key, Object value) {
gameState.put(key, value);
}
public Object getGameState(String key) {
return gameState.get(key);
}
// Create memento
public GameSave saveGame(String saveName) {
System.out.println("Saving game: " + saveName);
return new GameSave(playerName, level, score, health, inventory, gameState, saveName);
}
// Restore from memento
public void loadGame(GameSave save) {
this.playerName = save.getPlayerName();
this.level = save.getLevel();
this.score = save.getScore();
this.health = save.getHealth();
this.inventory = save.getInventory();
this.gameState = save.getGameState();
System.out.println("Game loaded: " + save.getSaveName());
printStatus();
}
public void printStatus() {
System.out.printf("\n=== %s Status ===%n", playerName);
System.out.printf("Level: %d%n", level);
System.out.printf("Score: %d%n", score);
System.out.printf("Health: %d/100%n", health);
System.out.printf("Inventory: %s%n", inventory);
System.out.printf("Game State: %s%n%n", gameState);
}
}
// Caretaker - Save Game Manager
class SaveGameManager {
private final Map<String, GameSave> saves = new LinkedHashMap<>();
private final String saveDirectory;
public SaveGameManager(String saveDirectory) {
this.saveDirectory = saveDirectory;
createSaveDirectory();
loadSavesFromDisk();
}
private void createSaveDirectory() {
File dir = new File(saveDirectory);
if (!dir.exists()) {
dir.mkdirs();
}
}
public void saveGame(GameSave save) {
saves.put(save.getSaveName(), save);
saveToDisk(save);
System.out.println("Game saved: " + save.getSaveName());
}
public GameSave loadGame(String saveName) {
GameSave save = saves.get(saveName);
if (save == null) {
System.out.println("Save not found: " + saveName);
return null;
}
return save;
}
public void deleteSave(String saveName) {
GameSave removed = saves.remove(saveName);
if (removed != null) {
deleteFromDisk(saveName);
System.out.println("Save deleted: " + saveName);
} else {
System.out.println("Save not found: " + saveName);
}
}
public List<String> listSaves() {
return new ArrayList<>(saves.keySet());
}
public void showSaves() {
System.out.println("\n=== Available Saves ===");
if (saves.isEmpty()) {
System.out.println("No saves available");
return;
}
saves.values().forEach(save -> {
System.out.printf("- %s (Level: %d, Score: %d, Health: %d)%n",
save.getSaveName(), save.getLevel(), save.getScore(), save.getHealth());
});
System.out.println();
}
// Serialization methods
private void saveToDisk(GameSave save) {
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(saveDirectory + "/" + save.getSaveName() + ".sav"))) {
oos.writeObject(save);
} catch (IOException e) {
System.err.println("Error saving game: " + e.getMessage());
}
}
private void loadSavesFromDisk() {
File dir = new File(saveDirectory);
File[] saveFiles = dir.listFiles((d, name) -> name.endsWith(".sav"));
if (saveFiles != null) {
for (File file : saveFiles) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
GameSave save = (GameSave) ois.readObject();
String saveName = file.getName().replace(".sav", "");
saves.put(saveName, save);
} catch (IOException | ClassNotFoundException e) {
System.err.println("Error loading save: " + e.getMessage());
}
}
}
}
private void deleteFromDisk(String saveName) {
File file = new File(saveDirectory + "/" + saveName + ".sav");
if (file.exists()) {
file.delete();
}
}
}
// Client code
public class GameSaveSystemDemo {
public static void main(String[] args) {
// Create game character
GameCharacter player = new GameCharacter("Hero");
SaveGameManager saveManager = new SaveGameManager("game_saves");
// Initial state
player.printStatus();
// Play the game
System.out.println("=== Gameplay ===");
player.addScore(50);
player.levelUp();
player.addItem("Super Potion");
player.takeDamage(30);
player.useItem("Potion");
// Save game
saveManager.saveGame(player.saveGame("initial_progress"));
player.printStatus();
// Continue playing
System.out.println("=== More Gameplay ===");
player.addScore(200);
player.levelUp();
player.takeDamage(60);
player.addItem("Magic Sword");
// Save again
saveManager.saveGame(player.saveGame("good_progress"));
player.printStatus();
// Show available saves
saveManager.showSaves();
// Simulate game crash and reload
System.out.println("=== Game Crash - Reloading ===");
GameSave loadedSave = saveManager.loadGame("initial_progress");
if (loadedSave != null) {
player.loadGame(loadedSave);
}
// Try different save
System.out.println("=== Loading Different Save ===");
loadedSave = saveManager.loadGame("good_progress");
if (loadedSave != null) {
player.loadGame(loadedSave);
}
// Create multiple saves
System.out.println("=== Multiple Saves ===");
player.addItem("Golden Key");
saveManager.saveGame(player.saveGame("with_key"));
player.levelUp();
player.addItem("Dragon Scale");
saveManager.saveGame(player.saveGame("dragon_hunt"));
saveManager.showSaves();
// Delete a save
System.out.println("=== Deleting Save ===");
saveManager.deleteSave("initial_progress");
saveManager.showSaves();
}
}
3. Document Editor with Undo/Redo
Advanced Memento for Document Editing
import java.util.ArrayDeque;
import java.util.Deque;
// Document Memento
class DocumentMemento {
private final String content;
private final String selection;
private final int cursorPosition;
private final String font;
private final int fontSize;
private final long timestamp;
private final String description;
public DocumentMemento(String content, String selection, int cursorPosition,
String font, int fontSize, String description) {
this.content = content;
this.selection = selection;
this.cursorPosition = cursorPosition;
this.font = font;
this.fontSize = fontSize;
this.timestamp = System.currentTimeMillis();
this.description = description;
}
// Getters
public String getContent() { return content; }
public String getSelection() { return selection; }
public int getCursorPosition() { return cursorPosition; }
public String getFont() { return font; }
public int getFontSize() { return fontSize; }
public long getTimestamp() { return timestamp; }
public String getDescription() { return description; }
@Override
public String toString() {
return String.format("DocumentState[%s: '%s...']",
description,
content.substring(0, Math.min(20, content.length())));
}
}
// Originator - Document
class Document {
private String content;
private String selection;
private int cursorPosition;
private String font;
private int fontSize;
public Document() {
this.content = "";
this.selection = "";
this.cursorPosition = 0;
this.font = "Arial";
this.fontSize = 12;
}
// Editing operations
public void insertText(String text) {
String before = content.substring(0, cursorPosition);
String after = content.substring(cursorPosition);
content = before + text + after;
cursorPosition += text.length();
selection = "";
System.out.println("Inserted: '" + text + "'");
}
public void deleteText(int length) {
if (cursorPosition >= length) {
String before = content.substring(0, cursorPosition - length);
String after = content.substring(cursorPosition);
content = before + after;
cursorPosition -= length;
System.out.println("Deleted " + length + " characters");
}
}
public void selectText(int start, int end) {
if (start >= 0 && end <= content.length() && start < end) {
selection = content.substring(start, end);
cursorPosition = end;
System.out.println("Selected: '" + selection + "'");
}
}
public void boldSelection() {
if (!selection.isEmpty()) {
content = content.replace(selection, "**" + selection + "**");
selection = "";
System.out.println("Applied bold to selection");
}
}
public void italicSelection() {
if (!selection.isEmpty()) {
content = content.replace(selection, "_" + selection + "_");
selection = "";
System.out.println("Applied italic to selection");
}
}
public void setFont(String font) {
this.font = font;
System.out.println("Font changed to: " + font);
}
public void setFontSize(int fontSize) {
this.fontSize = fontSize;
System.out.println("Font size changed to: " + fontSize);
}
public void setCursorPosition(int position) {
if (position >= 0 && position <= content.length()) {
this.cursorPosition = position;
System.out.println("Cursor moved to position: " + position);
}
}
// Create memento with description
public DocumentMemento saveState(String description) {
return new DocumentMemento(content, selection, cursorPosition, font, fontSize, description);
}
// Restore from memento
public void restoreState(DocumentMemento memento) {
this.content = memento.getContent();
this.selection = memento.getSelection();
this.cursorPosition = memento.getCursorPosition();
this.font = memento.getFont();
this.fontSize = memento.getFontSize();
System.out.println("Restored: " + memento.getDescription());
}
public void printState() {
System.out.printf("\n=== Document State ===%n");
System.out.printf("Content: '%s'%n", content);
System.out.printf("Selection: '%s'%n", selection);
System.out.printf("Cursor: %d%n", cursorPosition);
System.out.printf("Font: %s, Size: %d%n", font, fontSize);
// Show cursor position visually
if (!content.isEmpty()) {
String visual = content.substring(0, cursorPosition) + "|" +
content.substring(cursorPosition);
System.out.printf("Visual: '%s'%n%n", visual);
} else {
System.out.printf("Visual: '|'%n%n");
}
}
}
// Caretaker - Undo/Redo Manager
class UndoRedoManager {
private final Deque<DocumentMemento> undoStack = new ArrayDeque<>();
private final Deque<DocumentMemento> redoStack = new ArrayDeque<>();
private final int maxHistorySize;
public UndoRedoManager(int maxHistorySize) {
this.maxHistorySize = maxHistorySize;
}
public void saveState(DocumentMemento memento) {
undoStack.push(memento);
// Clear redo stack when new action is performed
redoStack.clear();
// Limit history size
if (undoStack.size() > maxHistorySize) {
// Remove oldest state (from the bottom of the stack)
ArrayDeque<DocumentMemento> temp = new ArrayDeque<>();
while (undoStack.size() > maxHistorySize - 1) {
temp.push(undoStack.pop());
}
undoStack.clear();
while (!temp.isEmpty()) {
undoStack.push(temp.pop());
}
}
System.out.println("State saved: " + memento.getDescription());
}
public DocumentMemento undo() {
if (undoStack.size() <= 1) {
System.out.println("Nothing to undo");
return null;
}
// Current state moves to redo stack
DocumentMemento current = undoStack.pop();
redoStack.push(current);
// Previous state becomes current
DocumentMemento previous = undoStack.peek();
System.out.println("Undo: " + previous.getDescription());
return previous;
}
public DocumentMemento redo() {
if (redoStack.isEmpty()) {
System.out.println("Nothing to redo");
return null;
}
DocumentMemento next = redoStack.pop();
undoStack.push(next);
System.out.println("Redo: " + next.getDescription());
return next;
}
public void showHistory() {
System.out.println("\n=== Undo/Redo History ===");
System.out.println("Undo Stack (" + undoStack.size() + " states):");
undoStack.forEach(state -> System.out.println(" " + state));
System.out.println("Redo Stack (" + redoStack.size() + " states):");
redoStack.forEach(state -> System.out.println(" " + state));
System.out.println();
}
public void clear() {
undoStack.clear();
redoStack.clear();
System.out.println("History cleared");
}
public boolean canUndo() {
return undoStack.size() > 1;
}
public boolean canRedo() {
return !redoStack.isEmpty();
}
}
// Client code
public class DocumentEditorDemo {
public static void main(String[] args) {
Document document = new Document();
UndoRedoManager history = new UndoRedoManager(10);
// Initial state
history.saveState(document.saveState("Initial empty document"));
document.printState();
// Edit document with saves
System.out.println("=== Editing Document ===");
document.insertText("Hello World");
history.saveState(document.saveState("Added 'Hello World'"));
document.setCursorPosition(6);
document.insertText("Beautiful ");
history.saveState(document.saveState("Inserted 'Beautiful '"));
document.selectText(0, 5);
document.boldSelection();
history.saveState(document.saveState("Applied bold to 'Hello'"));
document.setFontSize(16);
history.saveState(document.saveState("Increased font size"));
document.printState();
history.showHistory();
// Undo/Redo operations
System.out.println("=== Undo/Redo Operations ===");
document.restoreState(history.undo());
document.printState();
document.restoreState(history.undo());
document.printState();
document.restoreState(history.redo());
document.printState();
// Continue editing after undo/redo
System.out.println("=== Continue Editing ===");
document.insertText("! This is a demo.");
history.saveState(document.saveState("Added more text"));
document.setFont("Times New Roman");
history.saveState(document.saveState("Changed font"));
document.selectText(25, 29);
document.italicSelection();
history.saveState(document.saveState("Applied italic to 'demo'"));
document.printState();
history.showHistory();
// Multiple undos
System.out.println("=== Multiple Undos ===");
while (history.canUndo()) {
document.restoreState(history.undo());
}
document.printState();
// Multiple redos
System.out.println("=== Multiple Redos ===");
while (history.canRedo()) {
document.restoreState(history.redo());
}
document.printState();
}
}
4. Configuration Management System
Memento for Configuration State Management
import java.util.*;
import java.util.prefs.Preferences;
// Configuration Memento
class ConfigMemento {
private final Map<String, Object> settings;
private final String version;
private final String description;
private final long timestamp;
public ConfigMemento(Map<String, Object> settings, String version, String description) {
this.settings = new HashMap<>(settings);
this.version = version;
this.description = description;
this.timestamp = System.currentTimeMillis();
}
public Map<String, Object> getSettings() {
return new HashMap<>(settings);
}
public String getVersion() {
return version;
}
public String getDescription() {
return description;
}
public long getTimestamp() {
return timestamp;
}
@Override
public String toString() {
return String.format("Config[%s: %s, %d settings]",
version, description, settings.size());
}
}
// Originator - Application Configuration
class AppConfiguration {
private Map<String, Object> settings;
private String currentVersion;
public AppConfiguration() {
this.settings = new HashMap<>();
this.currentVersion = "1.0.0";
loadDefaultSettings();
}
private void loadDefaultSettings() {
settings.put("theme", "light");
settings.put("language", "en");
settings.put("autoSave", true);
settings.put("autoSaveInterval", 5);
settings.put("maxFileSize", 10485760);
settings.put("recentFiles", new ArrayList<String>());
settings.put("windowWidth", 1200);
settings.put("windowHeight", 800);
settings.put("showLineNumbers", true);
settings.put("fontSize", 14);
}
// Configuration operations
public void setSetting(String key, Object value) {
settings.put(key, value);
System.out.printf("Setting updated: %s = %s%n", key, value);
}
public Object getSetting(String key) {
return settings.get(key);
}
public void removeSetting(String key) {
settings.remove(key);
System.out.println("Setting removed: " + key);
}
public void setVersion(String version) {
this.currentVersion = version;
System.out.println("Version set to: " + version);
}
// Create memento
public ConfigMemento saveConfig(String description) {
return new ConfigMemento(settings, currentVersion, description);
}
// Restore from memento
public void restoreConfig(ConfigMemento memento) {
this.settings = memento.getSettings();
this.currentVersion = memento.getVersion();
System.out.println("Configuration restored: " + memento.getDescription());
}
// Export to properties format
public String exportToProperties() {
StringBuilder sb = new StringBuilder();
sb.append("# Application Configuration\n");
sb.append("# Version: ").append(currentVersion).append("\n");
sb.append("# Export Time: ").append(new Date()).append("\n\n");
settings.forEach((key, value) -> {
sb.append(key).append("=").append(value).append("\n");
});
return sb.toString();
}
// Import from properties (simplified)
public void importFromProperties(String properties) {
// Simple import implementation
String[] lines = properties.split("\n");
for (String line : lines) {
if (!line.trim().isEmpty() && !line.startsWith("#")) {
String[] parts = line.split("=", 2);
if (parts.length == 2) {
settings.put(parts[0].trim(), parseValue(parts[1].trim()));
}
}
}
System.out.println("Configuration imported from properties");
}
private Object parseValue(String value) {
// Simple value parsing
if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
return Boolean.parseBoolean(value);
}
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
// Try double
try {
return Double.parseDouble(value);
} catch (NumberFormatException e2) {
return value;
}
}
}
public void printConfig() {
System.out.printf("\n=== Configuration (v%s) ===%n", currentVersion);
settings.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(entry ->
System.out.printf(" %-20s = %s%n", entry.getKey(), entry.getValue()));
System.out.println();
}
}
// Caretaker - Configuration Manager
class ConfigManager {
private final List<ConfigMemento> history = new ArrayList<>();
private final Map<String, ConfigMemento> namedSnapshots = new HashMap<>();
private final int maxHistorySize;
public ConfigManager(int maxHistorySize) {
this.maxHistorySize = maxHistorySize;
}
public void saveState(ConfigMemento memento) {
history.add(memento);
// Limit history size
if (history.size() > maxHistorySize) {
history.remove(0); // Remove oldest
}
System.out.println("Configuration state saved: " + memento.getDescription());
}
public void saveNamedSnapshot(String name, ConfigMemento memento) {
namedSnapshots.put(name, memento);
System.out.println("Named snapshot saved: " + name);
}
public ConfigMemento undo() {
if (history.size() <= 1) {
System.out.println("Nothing to undo");
return null;
}
history.remove(history.size() - 1); // Remove current
ConfigMemento previous = history.get(history.size() - 1);
System.out.println("Undo to: " + previous.getDescription());
return previous;
}
public ConfigMemento getNamedSnapshot(String name) {
ConfigMemento snapshot = namedSnapshots.get(name);
if (snapshot == null) {
System.out.println("Snapshot not found: " + name);
}
return snapshot;
}
public void deleteNamedSnapshot(String name) {
if (namedSnapshots.remove(name) != null) {
System.out.println("Snapshot deleted: " + name);
} else {
System.out.println("Snapshot not found: " + name);
}
}
public void showHistory() {
System.out.println("\n=== Configuration History ===");
for (int i = 0; i < history.size(); i++) {
String marker = (i == history.size() - 1) ? "-> " : " ";
System.out.println(marker + history.get(i));
}
}
public void showSnapshots() {
System.out.println("\n=== Named Snapshots ===");
if (namedSnapshots.isEmpty()) {
System.out.println("No named snapshots");
return;
}
namedSnapshots.forEach((name, snapshot) -> {
System.out.printf(" %-15s : %s%n", name, snapshot.getDescription());
});
}
public void rollbackToVersion(String version) {
Optional<ConfigMemento> target = history.stream()
.filter(m -> m.getVersion().equals(version))
.reduce((first, second) -> second); // Get last occurrence
if (target.isPresent()) {
System.out.println("Rolling back to version: " + version);
// In real implementation, you'd return this for restoration
} else {
System.out.println("Version not found in history: " + version);
}
}
}
// Client code
public class ConfigManagementDemo {
public static void main(String[] args) {
AppConfiguration config = new AppConfiguration();
ConfigManager configManager = new ConfigManager(5);
// Initial state
configManager.saveState(config.saveConfig("Initial default configuration"));
config.printConfig();
// Modify configuration
System.out.println("=== Modifying Configuration ===");
config.setSetting("theme", "dark");
config.setSetting("fontSize", 16);
config.setSetting("autoSaveInterval", 10);
configManager.saveState(config.saveConfig("Dark theme with larger font"));
config.setVersion("1.1.0");
config.setSetting("language", "fr");
config.setSetting("showLineNumbers", false);
configManager.saveState(config.saveConfig("French language setup"));
// Save named snapshots
configManager.saveNamedSnapshot("work-setup",
config.saveConfig("Work configuration snapshot"));
config.setSetting("theme", "blue");
config.setSetting("fontSize", 18);
configManager.saveNamedSnapshot("presentation-mode",
config.saveConfig("Presentation mode configuration"));
config.printConfig();
configManager.showHistory();
configManager.showSnapshots();
// Export configuration
System.out.println("=== Exporting Configuration ===");
String exported = config.exportToProperties();
System.out.println(exported);
// Undo changes
System.out.println("=== Undo Operations ===");
config.restoreConfig(configManager.undo());
config.printConfig();
// Restore from named snapshot
System.out.println("=== Restoring from Snapshot ===");
ConfigMemento workSnapshot = configManager.getNamedSnapshot("work-setup");
if (workSnapshot != null) {
config.restoreConfig(workSnapshot);
config.printConfig();
}
// Show final state
configManager.showHistory();
configManager.showSnapshots();
}
}
Key Benefits of Memento Pattern
- Encapsulation Preservation: Doesn't expose object's internal state
- State Management: Easy undo/redo functionality
- Snapshot Capability: Can capture object state at any point
- Simplified Originator: Originator doesn't need to manage state storage
- Flexible Restoration: Can restore to any previous state
When to Use Memento Pattern
- When you need to implement undo/redo functionality
- When you need to save and restore object state
- When direct access to object's state would break encapsulation
- When you need to implement snapshots for checkpoints
- When state restoration should be independent of the object's lifecycle
This comprehensive guide demonstrates how the Memento Pattern provides robust state management capabilities while maintaining encapsulation and separation of concerns.