备忘录模式:文本编辑器撤销功能实现
行为型设计模式之备忘录模式
文章目录
- 行为型设计模式之备忘录模式
- 模式定义
- 核心思想
- 核心概念
- 三个核心角色
- 模式结构
- 工作流程图
- 实现示例
- 示例一:文本编辑器的撤销/重做功能
- 示例二:游戏存档系统
- 运行示例
- 状态演变图
- 应用场景
- 适用场景分析
- 具体应用领域
- 优缺点分析
- 优点对比表
- 缺点对比表
- 与其他模式对比
- 最佳实践
- 设计原则
- 性能优化策略
- 常见陷阱
- 在线资源
模式定义
备忘录模式(Memento Pattern)是一种行为型设计模式,它在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样,以后就可以将该对象恢复到原先保存的状态。
核心思想
- 状态保存与恢复:安全地保存和恢复对象的内部状态
- 封装性保护:不暴露对象的内部实现细节
- 时间旅行:支持撤销/重做等操作
核心概念
三个核心角色
备忘录模式涉及三个关键角色:
- 发起人(Originator):创建备忘录对象,需要恢复状态时使用备忘录
- 备忘录(Memento):存储发起人的内部状态
- 看护者(Caretaker):管理备忘录,但不能操作或检查备忘录的内容
模式结构
工作流程图
实现示例
示例一:文本编辑器的撤销/重做功能
using System;
using System.Collections.Generic;
using System.Text;// 备忘录接口 - 标记接口,防止外部直接访问
public interface IMemento
{DateTime Timestamp { get; }
}// 文本编辑器备忘录实现
public class TextEditorMemento : IMemento
{public string Content { get; }public int CursorPosition { get; }public DateTime Timestamp { get; }public string Operation { get; }public TextEditorMemento(string content, int cursorPosition, string operation){Content = content ?? string.Empty;CursorPosition = cursorPosition;Operation = operation ?? "Unknown";Timestamp = DateTime.Now;}public override string ToString(){return $"[{Timestamp:HH:mm:ss}] {Operation} - Length: {Content.Length}, Cursor: {CursorPosition}";}
}// 发起人 - 文本编辑器
public class TextEditor
{private StringBuilder _content;private int _cursorPosition;public TextEditor(){_content = new StringBuilder();_cursorPosition = 0;}// 获取当前文本内容public string Content => _content.ToString();// 获取当前光标位置public int CursorPosition => _cursorPosition;// 在光标位置插入文本public void InsertText(string text){if (string.IsNullOrEmpty(text)) return;_content.Insert(_cursorPosition, text);_cursorPosition += text.Length;Console.WriteLine($"插入文本: '{text}' 在位置 {_cursorPosition - text.Length}");Console.WriteLine($"当前内容: '{Content}'");}// 删除指定长度的文本public void DeleteText(int length){if (length <= 0 || _cursorPosition < length) return;int startPos = _cursorPosition - length;string deletedText = _content.ToString(startPos, length);_content.Remove(startPos, length);_cursorPosition = startPos;Console.WriteLine($"删除文本: '{deletedText}' 从位置 {startPos}");Console.WriteLine($"当前内容: '{Content}'");}// 移动光标public void MoveCursor(int position){if (position < 0 || position > _content.Length) return;_cursorPosition = position;Console.WriteLine($"光标移动到位置: {_cursorPosition}");}// 替换文本public void ReplaceText(int startIndex, int length, string newText){if (startIndex < 0 || startIndex >= _content.Length || length <= 0) return;string oldText = _content.ToString(startIndex, Math.Min(length, _content.Length - startIndex));_content.Remove(startIndex, Math.Min(length, _content.Length - startIndex));_content.Insert(startIndex, newText ?? string.Empty);_cursorPosition = startIndex + (newText?.Length ?? 0);Console.WriteLine($"替换文本: '{oldText}' -> '{newText}' 在位置 {startIndex}");Console.WriteLine($"当前内容: '{Content}'");}// 创建备忘录 - 保存当前状态public IMemento CreateMemento(string operation){Console.WriteLine($"创建备忘录: {operation}");return new TextEditorMemento(_content.ToString(), _cursorPosition, operation);}// 恢复备忘录 - 恢复到之前的状态public void RestoreMemento(IMemento memento){if (memento is not TextEditorMemento textMemento){throw new ArgumentException("无效的备忘录类型");}_content.Clear();_content.Append(textMemento.Content);_cursorPosition = textMemento.CursorPosition;Console.WriteLine($"恢复备忘录: {textMemento.Operation}");Console.WriteLine($"恢复后内容: '{Content}', 光标位置: {_cursorPosition}");}// 显示编辑器状态public void ShowStatus(){Console.WriteLine($"编辑器状态 - 内容: '{Content}', 光标位置: {_cursorPosition}, 长度: {_content.Length}");}
}// 看护者 - 历史管理器
public class HistoryManager
{private readonly List<IMemento> _history;private int _currentIndex;private readonly int _maxHistorySize;public HistoryManager(int maxHistorySize = 50){_history = new List<IMemento>();_currentIndex = -1;_maxHistorySize = maxHistorySize;}// 添加备忘录到历史记录public void SaveState(IMemento memento){// 如果当前不在历史记录的末尾,移除之后的所有记录if (_currentIndex < _history.Count - 1){_history.RemoveRange(_currentIndex + 1, _history.Count - _currentIndex - 1);}// 添加新的备忘录_history.Add(memento);_currentIndex++;// 如果超过最大历史记录数,移除最早的记录if (_history.Count > _maxHistorySize){_history.RemoveAt(0);_currentIndex--;}Console.WriteLine($"保存状态到历史记录 (位置: {_currentIndex + 1}/{_history.Count})");}// 撤销操作 - 返回上一个状态public IMemento Undo(){if (!CanUndo()){Console.WriteLine("无法撤销:已达到历史记录开始处");return null;}_currentIndex--;var memento = _history[_currentIndex];Console.WriteLine($"执行撤销操作 (位置: {_currentIndex + 1}/{_history.Count})");return memento;}// 重做操作 - 返回下一个状态public IMemento Redo(){if (!CanRedo()){Console.WriteLine("无法重做:已达到历史记录末尾");return null;}_currentIndex++;var memento = _history[_currentIndex];Console.WriteLine($"执行重做操作 (位置: {_currentIndex + 1}/{_history.Count})");return memento;}// 检查是否可以撤销public bool CanUndo(){return _currentIndex > 0;}// 检查是否可以重做public bool CanRedo(){return _currentIndex < _history.Count - 1;}// 获取历史记录信息public void ShowHistory(){Console.WriteLine($"\n=== 历史记录 (当前位置: {_currentIndex + 1}/{_history.Count}) ===");for (int i = 0; i < _history.Count; i++){string indicator = i == _currentIndex ? " -> " : " ";Console.WriteLine($"{indicator}{i + 1}. {_history[i]}");}Console.WriteLine("===========================================\n");}// 清空历史记录public void ClearHistory(){_history.Clear();_currentIndex = -1;Console.WriteLine("历史记录已清空");}// 获取历史记录统计public (int total, int current, bool canUndo, bool canRedo) GetStats(){return (_history.Count, _currentIndex + 1, CanUndo(), CanRedo());}
}
示例二:游戏存档系统
using System;
using System.Collections.Generic;
using System.Linq;// 游戏状态备忘录
public class GameStateMemento : IMemento
{public int Level { get; }public int Score { get; }public int Lives { get; }public int Health { get; }public Dictionary<string, int> Inventory { get; }public (float x, float y) PlayerPosition { get; }public DateTime Timestamp { get; }public string SaveName { get; }public GameStateMemento(int level, int score, int lives, int health, Dictionary<string, int> inventory, (float x, float y) position, string saveName){Level = level;Score = score;Lives = lives;Health = health;Inventory = new Dictionary<string, int>(inventory); // 深拷贝PlayerPosition = position;Timestamp = DateTime.Now;SaveName = saveName ?? $"存档_{Timestamp:yyyyMMdd_HHmmss}";}public override string ToString(){return $"{SaveName} - 等级:{Level}, 分数:{Score}, 生命:{Lives}, 血量:{Health}";}
}// 发起人 - 游戏引擎
public class GameEngine
{private int _level;private int _score;private int _lives;private int _health;private Dictionary<string, int> _inventory;private (float x, float y) _playerPosition;public GameEngine(){// 初始化游戏状态_level = 1;_score = 0;_lives = 3;_health = 100;_inventory = new Dictionary<string, int>();_playerPosition = (0f, 0f);}// 属性访问器public int Level => _level;public int Score => _score;public int Lives => _lives;public int Health => _health;public Dictionary<string, int> Inventory => new Dictionary<string, int>(_inventory);public (float x, float y) PlayerPosition => _playerPosition;// 游戏操作方法public void LevelUp(){_level++;_health = 100; // 升级时恢复满血Console.WriteLine($"恭喜升级!当前等级: {_level}");}public void AddScore(int points){_score += points;Console.WriteLine($"获得 {points} 分!当前总分: {_score}");}public void TakeDamage(int damage){_health = Math.Max(0, _health - damage);Console.WriteLine($"受到 {damage} 点伤害!当前血量: {_health}");if (_health == 0){LoseLife();}}public void LoseLife(){if (_lives > 0){_lives--;_health = 100; // 重生时恢复满血Console.WriteLine($"失去一条生命!剩余生命: {_lives}");}if (_lives == 0){Console.WriteLine("游戏结束!");}}public void AddToInventory(string item, int quantity){if (_inventory.ContainsKey(item)){_inventory[item] += quantity;}else{_inventory[item] = quantity;}Console.WriteLine($"获得物品: {item} x{quantity}");}public bool UseItem(string item, int quantity = 1){if (_inventory.ContainsKey(item) && _inventory[item] >= quantity){_inventory[item] -= quantity;if (_inventory[item] == 0){_inventory.Remove(item);}Console.WriteLine($"使用物品: {item} x{quantity}");return true;}Console.WriteLine($"物品不足: {item}");return false;}public void MovePlayer(float x, float y){_playerPosition = (x, y);Console.WriteLine($"玩家移动到位置: ({x}, {y})");}// 创建游戏存档public IMemento CreateSave(string saveName = null){Console.WriteLine($"创建游戏存档: {saveName ?? "自动存档"}");return new GameStateMemento(_level, _score, _lives, _health, _inventory, _playerPosition, saveName);}// 加载游戏存档public void LoadSave(IMemento memento){if (memento is not GameStateMemento gameMemento){throw new ArgumentException("无效的游戏存档");}_level = gameMemento.Level;_score = gameMemento.Score;_lives = gameMemento.Lives;_health = gameMemento.Health;_inventory = new Dictionary<string, int>(gameMemento.Inventory); // 深拷贝_playerPosition = gameMemento.PlayerPosition;Console.WriteLine($"加载游戏存档: {gameMemento.SaveName}");ShowGameState();}// 显示游戏状态public void ShowGameState(){Console.WriteLine("\n=== 当前游戏状态 ===");Console.WriteLine($"等级: {_level}");Console.WriteLine($"分数: {_score}");Console.WriteLine($"生命: {_lives}");Console.WriteLine($"血量: {_health}");Console.WriteLine($"位置: ({_playerPosition.x}, {_playerPosition.y})");if (_inventory.Any()){Console.WriteLine("背包物品:");foreach (var item in _inventory){Console.WriteLine($" {item.Key}: {item.Value}");}}else{Console.WriteLine("背包为空");}Console.WriteLine("==================\n");}
}// 看护者 - 存档管理器
public class SaveManager
{private readonly Dictionary<string, IMemento> _saveSlots;private readonly List<IMemento> _autoSaves;private readonly int _maxAutoSaves;public SaveManager(int maxAutoSaves = 10){_saveSlots = new Dictionary<string, IMemento>();_autoSaves = new List<IMemento>();_maxAutoSaves = maxAutoSaves;}// 保存到指定插槽public void SaveToSlot(string slotName, IMemento memento){_saveSlots[slotName] = memento;Console.WriteLine($"游戏已保存到插槽: {slotName}");}// 从指定插槽加载public IMemento LoadFromSlot(string slotName){if (_saveSlots.TryGetValue(slotName, out var memento)){Console.WriteLine($"从插槽加载游戏: {slotName}");return memento;}Console.WriteLine($"插槽 {slotName} 不存在");return null;}// 自动保存public void AutoSave(IMemento memento){_autoSaves.Add(memento);// 保持自动存档数量限制if (_autoSaves.Count > _maxAutoSaves){_autoSaves.RemoveAt(0);}Console.WriteLine($"自动保存完成 (自动存档数: {_autoSaves.Count})");}// 获取最新的自动存档public IMemento GetLatestAutoSave(){if (_autoSaves.Count == 0){Console.WriteLine("没有自动存档");return null;}var latest = _autoSaves.Last();Console.WriteLine("加载最新自动存档");return latest;}// 删除存档插槽public bool DeleteSlot(string slotName){if (_saveSlots.Remove(slotName)){Console.WriteLine($"删除存档插槽: {slotName}");return true;}Console.WriteLine($"插槽 {slotName} 不存在");return false;}// 列出所有存档public void ListSaves(){Console.WriteLine("\n=== 存档列表 ===");Console.WriteLine("手动存档:");if (_saveSlots.Any()){foreach (var save in _saveSlots){Console.WriteLine($" [{save.Key}] {save.Value}");}}else{Console.WriteLine(" 无");}Console.WriteLine($"自动存档: {_autoSaves.Count}/{_maxAutoSaves}");if (_autoSaves.Any()){for (int i = 0; i < _autoSaves.Count; i++){Console.WriteLine($" [Auto-{i + 1}] {_autoSaves[i]}");}}Console.WriteLine("===============\n");}// 获取存档统计信息public (int manualSaves, int autoSaves) GetSaveStats(){return (_saveSlots.Count, _autoSaves.Count);}// 清空所有存档public void ClearAllSaves(){_saveSlots.Clear();_autoSaves.Clear();Console.WriteLine("所有存档已清空");}
}
运行示例
class Program
{static void Main(string[] args){Console.WriteLine("=== 备忘录模式示例一:文本编辑器 ===\n");TextEditorDemo();Console.WriteLine("\n" + new string('=', 60) + "\n");Console.WriteLine("=== 备忘录模式示例二:游戏存档系统 ===\n");GameSaveDemo();}static void TextEditorDemo(){var editor = new TextEditor();var historyManager = new HistoryManager();// 初始状态editor.ShowStatus();historyManager.SaveState(editor.CreateMemento("初始状态"));// 执行一系列编辑操作editor.InsertText("Hello");historyManager.SaveState(editor.CreateMemento("插入 'Hello'"));editor.InsertText(" World");historyManager.SaveState(editor.CreateMemento("插入 ' World'"));editor.InsertText("!");historyManager.SaveState(editor.CreateMemento("插入 '!'"));editor.MoveCursor(5); // 移动到 "Hello" 和 " World" 之间editor.InsertText(" Beautiful");historyManager.SaveState(editor.CreateMemento("插入 ' Beautiful'"));// 显示历史记录historyManager.ShowHistory();// 演示撤销操作Console.WriteLine("\n--- 开始撤销操作 ---");for (int i = 0; i < 3; i++){var memento = historyManager.Undo();if (memento != null){editor.RestoreMemento(memento);editor.ShowStatus();}}// 演示重做操作Console.WriteLine("\n--- 开始重做操作 ---");for (int i = 0; i < 2; i++){var memento = historyManager.Redo();if (memento != null){editor.RestoreMemento(memento);editor.ShowStatus();}}// 在历史记录中间进行新操作Console.WriteLine("\n--- 在历史记录中间进行新操作 ---");editor.ReplaceText(0, 5, "Hi");historyManager.SaveState(editor.CreateMemento("替换 'Hello' 为 'Hi'"));historyManager.ShowHistory();}static void GameSaveDemo(){var game = new GameEngine();var saveManager = new SaveManager();// 显示初始游戏状态game.ShowGameState();// 玩游戏并创建存档点game.AddScore(100);game.AddToInventory("药水", 3);game.AddToInventory("剑", 1);game.MovePlayer(10.5f, 20.3f);// 手动存档saveManager.SaveToSlot("开始存档", game.CreateSave("第一关开始"));saveManager.AutoSave(game.CreateSave("自动存档1"));// 继续游戏game.LevelUp();game.AddScore(250);game.TakeDamage(30);game.UseItem("药水", 1);game.AddToInventory("盾牌", 1);game.MovePlayer(25.8f, 45.1f);// 创建更多存档saveManager.SaveToSlot("第二关", game.CreateSave("第二关进度"));saveManager.AutoSave(game.CreateSave("自动存档2"));// 模拟危险情况game.TakeDamage(70); // 血量归零,失去一条生命game.TakeDamage(50);game.AddScore(500);// 当前状态Console.WriteLine("\n--- 当前游戏状态(危险中)---");game.ShowGameState();// 显示所有存档saveManager.ListSaves();// 加载之前的安全存档Console.WriteLine("\n--- 加载安全存档 ---");var safeState = saveManager.LoadFromSlot("第二关");if (safeState != null){game.LoadSave(safeState);}// 继续游戏Console.WriteLine("\n--- 从安全点继续游戏 ---");game.AddScore(300);game.LevelUp();game.AddToInventory("宝石", 5);// 最终状态game.ShowGameState();// 创建最终存档saveManager.SaveToSlot("最终存档", game.CreateSave("游戏完成"));saveManager.ListSaves();}
}
状态演变图
应用场景
适用场景分析
具体应用领域
-
文本编辑器
- 撤销/重做操作
- 版本历史管理
- 自动保存功能
-
图形设计软件
- 操作历史记录
- 状态快照保存
- 非破坏性编辑
-
游戏开发
- 存档系统
- 关卡检查点
- 回合制游戏状态
-
数据库系统
- 事务回滚
- 快照隔离
- 时点恢复
-
Web应用
- 表单状态保存
- 操作历史记录
- 会话管理
优缺点分析
优点对比表
缺点对比表
与其他模式对比
最佳实践
设计原则
-
接口隔离
// 推荐:使用标记接口 public interface IMemento {DateTime Timestamp { get; } }// 具体实现对外不可见 internal class ConcreteMemento : IMemento {internal string State { get; }public DateTime Timestamp { get; }// ... }
-
封装保护
public class Originator {private string _internalState;// 只允许创建者访问内部状态public IMemento CreateMemento(){return new ConcreteMemento(_internalState);}public void RestoreMemento(IMemento memento){if (memento is ConcreteMemento concrete){_internalState = concrete.State;}} }
-
内存管理
public class HistoryManager : IDisposable {private readonly Queue<IMemento> _history;private readonly int _maxSize;public void AddMemento(IMemento memento){if (_history.Count >= _maxSize){_history.Dequeue(); // 移除最早的记录}_history.Enqueue(memento);}public void Dispose(){_history.Clear();} }
性能优化策略
- 延迟复制:只在需要时创建状态副本
- 增量备忘录:只保存变化的部分
- 压缩存储:对备忘录数据进行压缩
- 异步保存:在后台线程创建备忘录
常见陷阱
- 深拷贝与浅拷贝
- 内存泄漏风险
- 线程安全问题
- 性能过度优化
在线资源
- Microsoft C# 设计模式文档
- 备忘录模式详解
- C# 设计模式实现