【愚公系列】2023年10月 二十三种设计模式(十八)-备忘录模式(Memento Pattern)
🏆 作者简介,愚公搬代码
🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,阿里云专家博主,腾讯云优秀博主,掘金优秀博主,51CTO博客专家等。
🏆《近期荣誉》:2022年CSDN博客之星TOP2,2022年华为云十佳博主等。
🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
🏆🎉欢迎 👍点赞✍评论⭐收藏
🚀前言
设计模式(Design Pattern)是软件开发领域的宝贵经验,是多人反复借鉴和广泛应用的代码设计指导。它们是一系列经过分类和归纳的代码组织方法,旨在实现可重用性、可维护性和可理解性。使用设计模式,我们能够编写高质量的代码,使其更易于他人理解,并提供了代码可靠性的保证。
毫无疑问,设计模式对个人、团队和整个系统都带来了显著的益处。它们将代码开发提升到工程化水平,为软件工程构建提供了坚实的基础,就如同大厦的一块块精巧的砖石一样。在项目中明智地应用设计模式可以完美地解决各种复杂问题。每种设计模式都有相应的原理和最佳实践,它们描述了我们日常开发中不断遇到的问题,以及这些问题的核心解决方法。正是因为这种实用性和通用性,设计模式才能在软件开发中广泛地得以应用。设计模式是构建稳健、可扩展和可维护软件的关键工具,为开发者们提供了解决问题的智慧和指导。
🚀一、备忘录模式(Memento Pattern)
备忘录模式属于行为型设计模式,它的主要目的是在不破坏对象封装性的前提下,捕获对象的内部状态,并在对象之外保存这个状态。这样,我们可以随时将对象还原到之前保存的状态,就好像提供了一种“后悔药”的机制。
备忘录模式的关键点在于它允许我们记录对象的状态快照,以便将来可以恢复它们。这对于需要撤销、重做或历史记录功能的应用程序特别有用。
备忘录模式提供了一种有效的方式来管理对象状态的历史记录,使我们能够在需要时轻松地回滚到先前的状态,从而增强了系统的可维护性和灵活
🚀二、使用步骤
🔎1.角色
🦋1.1 原发器(Originator)
备忘录模式(Memento Pattern)中的原发器(Originator)是一个重要的概念,它代表了需要被保存状态的对象。原发器在备忘录模式中的作用如下:
状态的持有者:原发器是拥有状态的对象,它知道自己的内部状态是什么以及如何将状态保存到备忘录中。原发器通常具有一些可变的属性或数据,这些属性构成了它的状态。
创建备忘录:原发器负责创建备忘录对象,以便将当前状态保存到备忘录中。这通常通过一个特定的方法(例如
createMemento()
)来实现。恢复状态:原发器可以使用备忘录来还原自己的状态。通过将备忘录传递给原发器的一个方法(例如
restoreFromMemento(Memento memento)
),原发器可以将自己的状态恢复为备忘录所记录的状态。封装状态:原发器将自己的状态封装在备忘录中,从而实现了状态的封装和封装的分离。这有助于保持原发器的封装性,因为它的状态不会被外部对象直接访问或修改。
支持撤销和重做操作:通过保存状态的历史记录并能够还原到不同时间点的状态,原发器使得可以实现撤销和重做操作,增强了系统的可操作性和用户体验。
在备忘录模式中,原发器与备忘录(Memento)和负责管理备忘录的管理者(Caretaker)一起协作,以实现状态的保存和恢复功能。原发器的关键作用是允许状态的有效管理和封装,同时保持对象的封装性。这对于需要维护对象历史状态的应用程序非常有用。
🦋1.2 备忘录(Memento)
备忘录模式(Memento Pattern)中的备忘录(Memento)是一个重要的概念,它用于保存原发器(Originator)对象的内部状态,并且可以在需要时将原发器恢复到之前保存的状态。备忘录在该模式中的作用如下:
状态保存:备忘录的主要作用是存储原发器对象的内部状态信息。这些状态信息包括原发器的各种属性、数据或状态值。
封装状态:备忘录将状态信息封装在自身内部,这意味着外部对象无法直接访问或修改备忘录中的状态。这有助于维护原发器的封装性。
历史记录:备忘录允许原发器在不破坏封装性的情况下记录其状态的历史记录。这对于实现撤销(Undo)和重做(Redo)操作非常有用,因为原发器可以根据备忘录恢复到不同时间点的状态。
协助恢复状态:备忘录不仅保存了状态信息,还提供了一种机制,使得原发器可以使用备忘录来还原自己的状态。通常,原发器会提供一个方法,通过传递备忘录对象来进行状态恢复。
支持多个备份点:原发器可以创建多个备忘录对象,每个备忘录对象对应不同时间点的状态。这样,可以选择性地将原发器恢复到不同的状态,而不仅限于最近的状态。
实现撤销和重做:备忘录模式常用于实现撤销和重做功能,因为备忘录允许原发器在不同的历史状态之间切换,以便用户可以撤销先前的操作或重做已撤销的操作。
备忘录在备忘录模式中充当了状态的存储器和管理器的角色,它允许原发器保存和恢复状态,提供了一种有效的方式来处理对象状态的历史记录,从而增强了系统的可
🦋1.3 备忘录管理者(Caretaker)
备忘录模式(Memento Pattern)中的备忘录管理者(Caretaker)是一个重要的概念,它负责管理备忘录(Memento)对象,主要作用如下:
备份和恢复状态:备忘录管理者的主要作用是存储和管理备忘录对象,以便在需要时可以将原发器(Originator)对象的状态恢复到先前保存的状态。它充当了状态的保存和还原的协调者。
维护备忘录列表:备忘录管理者通常维护一个备忘录列表(或栈),其中包含不同时间点的备忘录对象。这使得原发器可以选择性地从不同的备忘录中还原状态。
保护备忘录的封装性:备忘录模式的设计目标之一是保护备忘录的封装性,确保只有原发器可以访问备忘录的内部状态。备忘录管理者负责管理备忘录,但不会访问备忘录的具体数据,从而维持了备忘录的封装性。
实现撤销和重做操作:备忘录管理者的存在使得原发器可以轻松实现撤销和重做操作。通过恢复不同备忘录中的状态,用户可以撤销之前的操作或重做已撤销的操作。
协助多个原发器:备忘录管理者可以同时管理多个原发器的备忘录。这对于涉及多个对象的系统非常有用,因为每个原发器可以有自己的备忘录管理者。
备忘录管理者在备忘录模式中扮演了关键的角色,它允许原发器保存状态历史记录、支持撤销和重做操作,同时维护备忘录的封装性。备忘录管理者与原发器和备忘录之间形成了协作,以实现状态的有效管理和恢复。这有助于增强系统的可维护性和用户体验。
🔎2.示例
命名空间MementoPattern中包含Memento备忘录类,Caretaker管理者类,象棋Chessman类。本案例将向大家演示如何撤销或重做对象棋位置的修改操作。本案例使用栈以支持多次撤销,并且重做支持前1次的多次撤销。本案例不支持重新设置棋子位置时所产生的分支。
public partial class Chessman {
private Point _position;
private Caretaker _caretaker = null;
public Point Position {
get => _position;
set {
_position = value;
_caretaker.Memento.Position = value; Console.WriteLine(
String.Format(Const.POSITION_INFO, _position.X, _position.Y));
}
}
public Chessman() : this(new Point(0, 0)) {
}
public Chessman(Point point) {
_caretaker = new Caretaker(new Memento());
Position = point;
}
}
象棋棋子类Chessman,内部维持棋子的位置,在设置棋子位置时将信息保存到管理者所管理的备忘录中。
public partial class Chessman {
public Chessman Undo(int step) {
try {
Console.WriteLine(Const.ARROW_LEFT);
Console.WriteLine($"Undo({step})!");
this._position = _caretaker.Memento.Undo(step);
Console.WriteLine(
String.Format(Const.POSITION_INFO, _position.X, _position.Y));
Console.WriteLine(Const.ARROW_RIGHT);
return this;
} catch(Exception ex) {
Console.WriteLine(ex.Message);
Console.WriteLine(Const.ARROW_RIGHT);
return this;
}
}
public Chessman Redo() {
try {
Console.WriteLine(Const.ARROW_LEFT);
Console.WriteLine("Redo()!");
this._position = _caretaker.Memento.Redo();
Console.WriteLine(
String.Format(Const.POSITION_INFO, _position.X, _position.Y));
Console.WriteLine(Const.ARROW_RIGHT);
return this;
} catch(Exception ex) {
Console.WriteLine(ex.Message);
Console.WriteLine(Const.ARROW_RIGHT);
return this;
}
}
}
象棋棋子类Chessman的第2部分(partial),支持按步数撤销位置,并且支持重做命令。
public partial class Memento {
private Point _position;
public Point Position {
get => _position;
set {
_position = value;
_history.Push(new RedoInfo { Position = value });
_redoList.Clear();
}
}
public Memento() {
_history = new Stack<RedoInfo>();
_redoList = new Stack<RedoInfo>();
}
private Stack<RedoInfo> _history = null;
private Stack<RedoInfo> _redoList = null;
public Point Undo(int step) {
int totalCount = 0;
List<string> temp = new List<string>();
foreach(var item in _history) {
if(string.IsNullOrWhiteSpace(item.GUID)) {
totalCount++;
} else {
if(!temp.Contains(item.GUID)) {
totalCount++;
temp.Add(item.GUID);
}
}
}
if(step >= totalCount) {
throw new InvalidOperationException("Too much steps!");
}
var guid = Guid.NewGuid().ToString("B");
for(int i = 1; i <= step; i++) {
Undo(guid);
}
return _position;
}
}
备忘录类Memento,内部维持对位置的引用并用2个栈分别管理历史和重做数据。
public partial class Memento {
private void UndoLoop(string guid) {
var history = _history.Pop();
history.GUID = guid;
_redoList.Push(history);
_position = _history.Peek().Position;
}
private void Undo(string guid) {
var temp = _history.Peek().GUID;
if(string.IsNullOrWhiteSpace(temp)) {
UndoLoop(guid);
} else {
while(_history.Peek().GUID == temp) {
UndoLoop(guid);
}
}
}
public Point Redo() {
if(_redoList.Count == 0) {
throw new InvalidOperationException("You can not redo now!");
}
var guid = _redoList.Peek().GUID;
while(_redoList.Count != 0 && _redoList.Peek().GUID == guid) {
_history.Push(_redoList.Pop());
_position = _history.Peek().Position;
}
return _position;
}
}
备忘录类Memento的第2部分(partial),包含了按步数撤销和重做的具体逻辑。
public class Caretaker {
public Memento Memento { get; set; }
public Caretaker(Memento memento) {
Memento = memento;
}
}
管理者Caretaker类,管理者负责维持备忘录。
public class RedoInfo {
public Point Position { get; set; }
public string GUID { get; set; }
}
重做RedoInfo类,按GUID的值判断是否是同一“批次”被撤销的。
public class Const {
public const string POSITION_INFO = "Current position is ({0},{1})!";
public const string ARROW_LEFT = "<---------------------------";
public const string ARROW_RIGHT = "--------------------------->";
}
常量类Const,维护一些本案例中经常用到的字符串。在实际开发过程中不应当有此类,应该将相应的常量放在具体要使用的类中。2017年,阿里发布《阿里巴巴Java开发手册》,其中有一节提到此准则,所有使用面向对象编程语言的开发人员都应当遵从。
public class Program {
private static Chessman _chessman = null;
public static void Main(string[] args) {
_chessman = new Chessman(new Point(1, 10));
_chessman.Position = new Point(2, 20);
_chessman.Position = new Point(3, 30);
_chessman.Position = new Point(4, 40);
_chessman.Position = new Point(5, 50);
_chessman.Position = new Point(9, 40);
_chessman.Undo(1)
.Undo(2)
.Undo(1)
.Redo()
.Redo()
.Redo()
.Redo()
.Redo()
.Undo(6)
.Undo(5)
.Undo(4);
Console.ReadKey();
}
}
以上是调用方的代码演示,撤销和重做方法经过特殊处理以支持方法链。以下是这个案例的输出结果:
Current position is (1,10)!
Current position is (2,20)!
Current position is (3,30)!
Current position is (4,40)!
Current position is (5,50)!
Current position is (9,40)!
<---------------------------
Undo(1)!
Current position is (5,50)!
--------------------------->
<---------------------------
Undo(2)!
Current position is (3,30)!
--------------------------->
<---------------------------
Undo(1)!
Current position is (2,20)!
--------------------------->
<---------------------------
Redo()!
Current position is (3,30)!
--------------------------->
<---------------------------
Redo()!
Current position is (5,50)!
--------------------------->
<---------------------------
Redo()!
Current position is (9,40)!
--------------------------->
<---------------------------
Redo()!
You can not redo now!
--------------------------->
<---------------------------
Redo()!
You can not redo now!
--------------------------->
<---------------------------
Undo(6)!
Too much steps!
--------------------------->
<---------------------------
Undo(5)!
Too much steps!
--------------------------->
<---------------------------
Undo(4)!
Current position is (1,10)!
--------------------------->
🚀总结
🔎1.优点
备忘录模式(Memento Pattern)具有许多优点,使其成为一种有用的设计模式。以下是备忘录模式的一些主要优点:
状态保存和恢复:备忘录模式允许原发器对象保存其内部状态的历史记录,并能够在需要时恢复到先前的状态。这对于实现撤销、重做和历史记录功能非常有用。
封装性:备忘录模式有助于维护对象的封装性,因为备忘录对象将状态信息封装在内部,只有原发器对象可以访问这些信息。这有助于防止外部对象直接访问或修改状态。
灵活性:备忘录模式使得可以保存多个时间点的状态,而不仅限于最后一个状态。这增加了系统的灵活性,因为用户可以选择从不同的备忘录中恢复状态。
撤销和重做操作:备忘录模式为实现撤销和重做操作提供了一个简单而强大的机制。用户可以撤销之前的操作,然后再次重做它们,从而增强了用户体验。
简化原发器:备忘录模式可以减轻原发器对象的负担,因为它不需要跟踪和管理所有的历史状态。状态的保存和管理由备忘录和备忘录管理者负责。
支持多个原发器:备忘录管理者可以同时管理多个原发器对象的备忘录,使得可以在一个系统中有效地管理多个对象的状态。
可扩展性:备忘录模式易于扩展。可以轻松地添加新的备忘录类型或修改备忘录对象的结构,而不会影响到原发器或备忘录管理者。
备忘录模式提供了一种有效的方式来管理对象状态的历史记录,增强了系统的可维护性、灵活性和用户体验。它在需要保存和恢复对象状态的情况下非常有用,特别是在需要支持撤销和重做操作的应用程序中。
🔎2.缺点
备忘录模式(Memento Pattern)虽然有许多优点,但也存在一些缺点:
资源消耗:在某些情况下,备忘录模式可能会导致内存消耗增加,特别是在需要保存大量状态信息的情况下。每个备忘录对象都需要占用一定的内存空间,如果状态较大或备忘录对象较多,可能会导致资源占用较高。
性能开销:在频繁保存和恢复状态的情况下,备忘录模式可能会引入性能开销。每次保存状态都需要创建备忘录对象并将其存储,而每次恢复状态都需要从备忘录中获取并还原。这可能会在一定程度上影响程序的性能。
管理多个备忘录:如果系统中涉及多个原发器对象,而每个原发器都需要管理多个备忘录对象,可能会增加系统的复杂性和维护难度。
安全性:备忘录模式可能会导致一些安全性问题,特别是在需要保护敏感信息的情况下。备忘录对象本身可能会包含原发器的所有状态信息,如果不妥善处理,可能会导致信息泄漏。
适用性限制:备忘录模式更适用于需要保存对象历史状态的情况,对于一些简单的情况可能显得过于繁琐和不必要。
备忘录对象的生命周期:需要注意管理备忘录对象的生命周期,避免因为无法正确管理备忘录对象而导致资源泄漏或状态丢失的问题。
总的来说,备忘录模式在一些场景下可以提供有力的支持,但在使用时需要权衡其优缺点,确保选择合适的设计模式来解决特定的问题。备忘录模式特别适用于
🔎3.使用场景
备忘录模式(Memento Pattern)适用于以下场景:
需要保存和恢复对象状态的情况:当一个对象的状态需要在不破坏其封装性的情况下保存,并且可能需要在将来的某个时间点恢复到先前的状态时,备忘录模式非常有用。这种情况下,备忘录可以用来保存对象的状态历史记录。
支持撤销和重做操作:备忘录模式常用于实现撤销(Undo)和重做(Redo)功能。用户可以撤销之前的操作,并可以再次重做它们,而不需要重新执行操作。
历史记录和版本控制:备忘录模式可用于创建历史记录或版本控制系统,例如文档编辑器中的历史记录功能,可以记录文档在不同时间点的状态。
编辑器和IDE:文本编辑器、图形编辑器以及集成开发环境(IDE)通常使用备忘录模式来保存用户的编辑操作历史,以便支持撤销和重做操作。
游戏状态管理:在游戏开发中,备忘录模式可以用于保存游戏状态,以便在玩家退出游戏并重新进入时恢复到上一次的状态。
数据库事务管理:数据库系统可以使用备忘录模式来实现事务管理,允许在事务回滚时恢复到之前的数据库状态。
配置管理:备忘录模式可以用于保存和还原应用程序的配置信息,以便在需要时重新加载应用程序的配置。
电子商务购物车:在电子商务应用中,备忘录模式可以用于保存购物车的状态,以便用户可以在不同时间点查看和恢复购物车中的商品。
备忘录模式在需要保存和管理对象状态历史记录,支持撤销和重做操作,或实现历史记录功能的场景中非常有用。它有助于提高应用程序的可维护性和用户体验,同时保护对象的封装
🚀感谢:给读者的一封信
亲爱的读者,
我在这篇文章中投入了大量的心血和时间,希望为您提供有价值的内容。这篇文章包含了深入的研究和个人经验,我相信这些信息对您非常有帮助。
如果您觉得这篇文章对您有所帮助,我诚恳地请求您考虑赞赏1元钱的支持。这个金额不会对您的财务状况造成负担,但它会对我继续创作高质量的内容产生积极的影响。
我之所以写这篇文章,是因为我热爱分享有用的知识和见解。您的支持将帮助我继续这个使命,也鼓励我花更多的时间和精力创作更多有价值的内容。
如果您愿意支持我的创作,请扫描下面二维码,您的支持将不胜感激。同时,如果您有任何反馈或建议,也欢迎与我分享。
再次感谢您的阅读和支持!
最诚挚的问候, “愚公搬代码”
- 点赞
- 收藏
- 关注作者
评论(0)