在C#开发过程中,很多业务场景需要支持对象状态的撤销与恢复功能,比如文本编辑器的撤销输入、绘图软件的重做操作、配置项的回滚修改等。备忘录模式可以在不暴露对象内部细节的前提下,记录对象的历史状态,满足这类需求。

备忘录模式的核心角色
备忘录模式的实现需要三个核心角色配合,各自的职责明确,共同完成状态保存与恢复的逻辑:
- 发起者(Originator):需要保存状态的对象,负责创建包含自身状态的备忘录,也可以通过备忘录恢复自身状态。
- 备忘录(Memento):存储发起者的内部状态,防止外部直接访问其内部数据,通常只暴露给发起者和管理者。
- 管理者(Caretaker):负责保存多个备忘录对象,提供对备忘录的存取操作,不修改备忘录的内容。
基础实现示例
下面通过一个文本编辑器的场景来演示完整的实现过程,文本编辑器需要支持输入内容的撤销操作。
1. 定义备忘录类
备忘录类用于存储发起者的状态,这里存储文本内容:
// 备忘录类,存储文本状态
public class TextMemento
{
// 保存的文本内容
public string Content { get; private set; }
// 构造函数,初始化保存的内容
public TextMemento(string content)
{
Content = content;
}
}
2. 定义发起者类
发起者是文本编辑器本身,包含当前的文本内容,以及创建备忘录、从备忘录恢复状态的方法:
// 发起者类,对应文本编辑器
public class TextEditor
{
// 当前文本内容
public string CurrentContent { get; set; }
// 创建备忘录,保存当前状态
public TextMemento CreateMemento()
{
return new TextMemento(CurrentContent);
}
// 从备忘录恢复状态
public void RestoreFromMemento(TextMemento memento)
{
if (memento != null)
{
CurrentContent = memento.Content;
}
}
}
3. 定义管理者类
管理者类负责维护备忘录的历史列表,支持添加、获取指定索引的备忘录:
using System.Collections.Generic;
// 管理者类,管理备忘录历史
public class Caretaker
{
// 存储备忘录的列表
private List<TextMemento> _mementoList = new List<TextMemento>();
// 添加备忘录到历史列表
public void AddMemento(TextMemento memento)
{
_mementoList.Add(memento);
}
// 获取指定索引的备忘录,索引从0开始
public TextMemento GetMemento(int index)
{
if (index >= 0 && index < _mementoList.Count)
{
return _mementoList[index];
}
return null;
}
// 获取最后一个备忘录,用于撤销操作
public TextMemento GetLastMemento()
{
if (_mementoList.Count == 0)
{
return null;
}
return _mementoList[_mementoList.Count - 1];
}
// 移除最后一个备忘录,当撤销后不需要保留该状态时可以使用
public void RemoveLastMemento()
{
if (_mementoList.Count > 0)
{
_mementoList.RemoveAt(_mementoList.Count - 1);
}
}
}
4. 调用示例
下面模拟文本编辑器的使用过程,演示状态保存与撤销恢复的逻辑:
using System;
class Program
{
static void Main(string[] args)
{
// 初始化文本编辑器和备忘录管理者
TextEditor editor = new TextEditor();
Caretaker caretaker = new Caretaker();
// 第一次输入内容,保存状态
editor.CurrentContent = "第一次输入的内容";
Console.WriteLine("当前内容:" + editor.CurrentContent);
caretaker.AddMemento(editor.CreateMemento());
// 第二次输入内容,保存状态
editor.CurrentContent = "第二次输入的内容";
Console.WriteLine("当前内容:" + editor.CurrentContent);
caretaker.AddMemento(editor.CreateMemento());
// 第三次输入内容,保存状态
editor.CurrentContent = "第三次输入的内容";
Console.WriteLine("当前内容:" + editor.CurrentContent);
caretaker.AddMemento(editor.CreateMemento());
// 执行撤销操作,恢复到上一次的状态
TextMemento lastMemento = caretaker.GetLastMemento();
if (lastMemento != null)
{
editor.RestoreFromMemento(lastMemento);
Console.WriteLine("撤销后内容:" + editor.CurrentContent);
// 移除最后一个备忘录,避免重复撤销
caretaker.RemoveLastMemento();
}
// 再次撤销,恢复到第一次输入后的状态
lastMemento = caretaker.GetLastMemento();
if (lastMemento != null)
{
editor.RestoreFromMemento(lastMemento);
Console.WriteLine("再次撤销后内容:" + editor.CurrentContent);
}
}
}
实现注意事项
在实际使用备忘录模式时,需要注意以下几点:
- 备忘录的存储内容需要根据发起者的状态确定,不要存储无关数据,避免占用过多内存。
- 如果状态数据较大,可以考虑使用序列化方式存储备忘录,或者限制历史状态的保存数量。
- 备忘录的访问权限要做好控制,除了发起者和管理者,其他对象不应该直接修改备忘录的内容。
- 如果发起者状态变化频繁,需要合理控制保存状态的频率,避免性能问题。
扩展场景
除了文本编辑器的撤销功能,备忘录模式还可以应用在更多场景中:
- 游戏进度的保存与加载,将玩家的游戏状态保存到备忘录,后续读取恢复。
- 表单操作的回滚,用户修改表单后放弃修改,恢复到修改前的状态。
- 事务操作的回滚,当操作失败时,恢复到事务开始前的系统状态。