当前位置: 首页 > java >正文

备忘录模式:文本编辑器撤销功能实现

行为型设计模式之备忘录模式

文章目录

  • 行为型设计模式之备忘录模式
    • 模式定义
      • 核心思想
    • 核心概念
      • 三个核心角色
    • 模式结构
      • 工作流程图
    • 实现示例
      • 示例一:文本编辑器的撤销/重做功能
      • 示例二:游戏存档系统
      • 运行示例
    • 状态演变图
    • 应用场景
      • 适用场景分析
      • 具体应用领域
    • 优缺点分析
      • 优点对比表
      • 缺点对比表
      • 与其他模式对比
    • 最佳实践
      • 设计原则
      • 性能优化策略
      • 常见陷阱
    • 在线资源

模式定义

备忘录模式(Memento Pattern)是一种行为型设计模式,它在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样,以后就可以将该对象恢复到原先保存的状态。

核心思想

  • 状态保存与恢复:安全地保存和恢复对象的内部状态
  • 封装性保护:不暴露对象的内部实现细节
  • 时间旅行:支持撤销/重做等操作

核心概念

三个核心角色

备忘录模式涉及三个关键角色:

  1. 发起人(Originator):创建备忘录对象,需要恢复状态时使用备忘录
  2. 备忘录(Memento):存储发起人的内部状态
  3. 看护者(Caretaker):管理备忘录,但不能操作或检查备忘录的内容

模式结构

Originator
state string
createMemento() : Memento
restoreMemento(memento Memento) : void
setState(state string) : void
getState() : string
Memento
state string
getState() : string
Caretaker
mementoList List
addMemento(memento Memento) : void
getMemento(index int) : Memento
removeMemento(index int) : void

工作流程图

Client Caretaker Originator Memento 设置状态 保存状态 createMemento() new Memento(state) memento memento 存储memento 后续恢复操作 恢复状态 获取memento restoreMemento(memento) getState() state 恢复内部状态 Client Caretaker Originator Memento

实现示例

示例一:文本编辑器的撤销/重做功能

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();}
}

状态演变图

看护者管理
撤销
撤销
重做
备忘录1
备忘录2
备忘录3
备忘录4
初始状态
状态1
状态2
状态3
状态4

应用场景

适用场景分析

备忘录模式应用场景
撤销/重做功能
状态回滚需求
检查点机制
版本控制系统
文本编辑器
图形设计软件
代码编辑器
数据库事务
游戏存档
配置管理
长时间计算
批处理作业
模拟仿真
Git版本控制
文档版本管理
代码历史记录

具体应用领域

  1. 文本编辑器

    • 撤销/重做操作
    • 版本历史管理
    • 自动保存功能
  2. 图形设计软件

    • 操作历史记录
    • 状态快照保存
    • 非破坏性编辑
  3. 游戏开发

    • 存档系统
    • 关卡检查点
    • 回合制游戏状态
  4. 数据库系统

    • 事务回滚
    • 快照隔离
    • 时点恢复
  5. Web应用

    • 表单状态保存
    • 操作历史记录
    • 会话管理

优缺点分析

优点对比表

备忘录模式优点
封装性保护
简化发起人
灵活的恢复机制
支持多重撤销
不破坏对象封装
隐藏实现细节
发起人职责单一
简化状态管理
任意时间点恢复
选择性状态恢复
历史记录管理
批量操作回滚

缺点对比表

备忘录模式缺点
内存开销
性能影响
复杂性增加
生命周期管理
存储多个状态副本
大对象内存消耗
频繁创建备忘录
深拷贝性能开销
三个角色协调
状态管理复杂
备忘录清理策略
内存泄漏风险

与其他模式对比

备忘录模式 vs 其他模式
vs 命令模式
vs 原型模式
vs 快照模式
备忘录: 状态恢复
命令: 操作撤销
备忘录: 状态保存
原型: 对象复制
备忘录: 完整状态
快照: 轻量级状态

最佳实践

设计原则

  1. 接口隔离

    // 推荐:使用标记接口
    public interface IMemento
    {DateTime Timestamp { get; }
    }// 具体实现对外不可见
    internal class ConcreteMemento : IMemento
    {internal string State { get; }public DateTime Timestamp { get; }// ...
    }
    
  2. 封装保护

    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;}}
    }
    
  3. 内存管理

    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();}
    }
    

性能优化策略

  1. 延迟复制:只在需要时创建状态副本
  2. 增量备忘录:只保存变化的部分
  3. 压缩存储:对备忘录数据进行压缩
  4. 异步保存:在后台线程创建备忘录

常见陷阱

  1. 深拷贝与浅拷贝
  2. 内存泄漏风险
  3. 线程安全问题
  4. 性能过度优化

在线资源

  • Microsoft C# 设计模式文档
  • 备忘录模式详解
  • C# 设计模式实现

在这里插入图片描述

http://www.xdnf.cn/news/14178.html

相关文章:

  • 2025年渗透测试面试题总结-字节跳动[实习]安全研究员(题目+回答)
  • 浏览器 报502 网关错误,解决方法2
  • 论文精读Lami-Detr:Open-Vocabulary Detection with Language Model Instruction
  • 芯片的起点——从硅到晶圆制造
  • 用Python写一个可视化大屏
  • 简说ping、telnet、netcat
  • 论文阅读-单目视觉惯性系统时间标定
  • MySQL 锁学习笔记
  • 计算机网络-自顶向下—第二章应用层-重点复习笔记
  • 在C++中的封装(Encapsulation)
  • Linux学习笔记:PCIe内核篇(1):初始化与枚举流程
  • 第1章 C# 和 .NET 框架 笔记
  • MCP简介和应用
  • 第十七章 Linux之大数据定制篇——Shell编程
  • ES知识合集(四):高级篇
  • 20250614让NanoPi NEO core开发板在Ubuntu core16.04系统下使用耳机播音测试
  • 「Linux文件及目录管理」目录结构及显示类命令
  • Python虚拟环境的使用
  • SpringBoot源码解析(十一):条件注解@ConditionalOnClass的匹配逻辑
  • 如何调优Kafka
  • LeetCode 第71题 简化路径(繁琐)
  • thinkphp8提升之查询
  • Nature Machine Intelligence 北京通研院朱松纯团队开发视触觉传感仿人灵巧手,实现类人自适应抓取
  • 开心灿烂go开发面试题
  • 如何自动化测试 DependencyMatcher 规则效果(CI/CD 集成最佳实践)
  • 免费OCPP协议测试工具
  • FreeRTOS定时器
  • C++/OpenCV地砖识别系统结合 Libevent 实现网络化 AI 接入
  • 如何写出优秀的单元测试?
  • 17.vue.js响应式和dom更新