学习设计模式第二十一 - 备忘录模式
示例代码来自DoFactory。
概述
当你需要让对象返回之前的状态时,就使用备忘录模式。
意图
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
UML
图1 备忘录模式的UML图
参与者
这个模式涉及的类或对象:
-
Memento
-
存储Originator对象的内部状态。根据originator的需要不同,memento可能存储或是大量或是很少originator的内部状态。
-
阻止除了originator外其它对象的地访问。Memento实际上有两个接口。Caretaker知道Memento那个开放小的接口 – 它只可以将memento传递给其它对象。相反,Originator使用一个更开发的接口,通过这个接口可以访问所有需要数据,并使用这些数据将自身还原到之前的状态。理论上,只有产生memento的originator可以被允许访问memento的内部状态。
-
Originator
-
创建包含当前内部状态快照的一个memento。
-
使用memento来还原其内部状态
-
Caretaker
-
负责妥善保管memento对象
-
从不操作或检查memento的内容。
适用性
备忘录模式的意图是给一个对象提供存储和还原功能。存储对象状态所采用的机制取决于存储所需持续的时间,可能是几秒,几天甚至是几年。可选的存储包括内存(如Session),临时文件或数据库。
在抽象意义上,你可以将数据库视为备忘录模式的一个实现。然而,需要这种模式最常见的原因是捕捉对象状态的一个快照,从而任何接下来步骤中的更改都可以被轻松的撤销,如果需要。在.NET中最常见的实现是通过序列化反序列化对象来保存或还原一个对象的状态。
本质上,Memento是一个存储对象状态的小型仓库。几个你要还原对象到之前一个状态的场景如:保存并还原电脑游戏中人物的状态,或在数据库中实现一个撤销操作。
下面是一些具体的适用备忘录模式的场景:
-
必须保存一个对象在某一个时刻的(部分)状态, 这样以后需要时它才能恢复到先前的状态。
-
如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
DoFactory GoF代码
下面示例代码展示了使用备忘录模式临时存储一个对象的内部状态并进行还原。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | // Memento pattern // Structural example using System; namespace DoFactory.GangOfFour.Memento.Structural { // MainApp test application class MainApp { static void Main() { Originator o = new Originator(); o.State = "On" ; // Store internal state Caretaker c = new Caretaker(); c.Memento = o.CreateMemento(); // Continue changing originator o.State = "Off" ; // Restore saved state o.SetMemento(c.Memento); // Wait for user Console.ReadKey(); } } // "Originator" class Originator { private string _state; // Property public string State { get { return _state; } set { _state = value; Console.WriteLine( "State = " + _state); } } // Creates memento public Memento CreateMemento() { return ( new Memento(_state)); } // Restores original state public void SetMemento(Memento memento) { Console.WriteLine( "Restoring state..." ); State = memento.State; } } // "Memento" class Memento { private string _state; // Constructor public Memento( string state) { this ._state = state; } // Gets or sets state public string State { get { return _state; } } } // "Caretaker" class Caretaker { private Memento _memento; // Gets or sets memento public Memento Memento { set { _memento = value; } get { return _memento; } } } } |
实际应用的例子展示了使用备忘录模式临时存储SalesProspect对象的内部状态并进行还原。
例子中涉及到的类与备忘录模式中标准的类对应关系如下:
-
Memento - Memento
-
Originator - SalesProspect
-
Caretaker - Caretaker
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | // Memento pattern // Real World example using System; namespace DoFactory.GangOfFour.Memento.RealWorld { // MainApp test application class MainApp { static void Main() { SalesProspect s = new SalesProspect(); s.Name = "Noel van Halen" ; s.Phone = "(412) 256-0990" ; s.Budget = 25000.0; // Store internal state ProspectMemory m = new ProspectMemory(); m.Memento = s.SaveMemento(); // Continue changing originator s.Name = "Leo Welch" ; s.Phone = "(310) 209-7111" ; s.Budget = 1000000.0; // Restore saved state s.RestoreMemento(m.Memento); // Wait for user Console.ReadKey(); } } // "Originator" class SalesProspect { private string _name; private string _phone; private double _budget; // Gets or sets name public string Name { get { return _name; } set { _name = value; Console.WriteLine( "Name: " + _name); } } // Gets or sets phone public string Phone { get { return _phone; } set { _phone = value; Console.WriteLine( "Phone: " + _phone); } } // Gets or sets budget public double Budget { get { return _budget; } set { _budget = value; Console.WriteLine( "Budget: " + _budget); } } // Stores memento public Memento SaveMemento() { Console.WriteLine( "\nSaving state --\n" ); return new Memento(_name, _phone, _budget); } // Restores memento public void RestoreMemento(Memento memento) { Console.WriteLine( "\nRestoring state --\n" ); this .Name = memento.Name; this .Phone = memento.Phone; this .Budget = memento.Budget; } } // "Memento" class Memento { private string _name; private string _phone; private double _budget; // Constructor public Memento( string name, string phone, double budget) { this ._name = name; this ._phone = phone; this ._budget = budget; } // Gets or sets name public string Name { get { return _name; } set { _name = value; } } // Gets or set phone public string Phone { get { return _phone; } set { _phone = value; } } // Gets or sets budget public double Budget { get { return _budget; } set { _budget = value; } } } // "Caretaker" class ProspectMemory { private Memento _memento; // Property public Memento Memento { set { _memento = value; } get { return _memento; } } } } |
这段.NET优化版示例实现了与上面实际应用例子相同的功能,但更多的采用了更现代的.NET内置的特性。在这个例子中Memento更通用。它可以接受任何可以序列化的对象,存储并还原它们。Originator类使用Serializable特性修饰。注意这种方式并不一定是最高效的,因为序列化与反序列化的步骤相对昂贵。例子中也使用了.NET 3.0引入的特性,包括ProspectMemery中用到的自动属性及SalesProspect中用到的对象初始化器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | // Memento Design Pattern // .NET optimized example using System; using System.IO; using System.Runtime.Serialization.Formatters.Soap; namespace DoFactory.GangOfFour.Memento.NETOptimized { class MainApp { static void Main() { // Init sales prospect through object initialization var s = new SalesProspect { Name = "Joel van Halen" , Phone = "(412) 256-0990" , Budget = 25000.0 }; // Store internal state var m = new ProspectMemory(); m.Memento = s.SaveMemento(); // Change originator s.Name = "Leo Welch" ; s.Phone = "(310) 209-7111" ; s.Budget = 1000000.0; // Restore saved state s.RestoreMemento(m.Memento); // Wait for user Console.ReadKey(); } } // "Originator" [Serializable] class SalesProspect { private string _name; private string _phone; private double _budget; // Gets or sets name public string Name { get { return _name; } set { _name = value; Console.WriteLine( "Name: " + _name); } } // Gets or sets phone public string Phone { get { return _phone; } set { _phone = value; Console.WriteLine( "Phone: " + _phone); } } // Gets or sets budget public double Budget { get { return _budget; } set { _budget = value; Console.WriteLine( "Budget: " + _budget); } } // Stores (serializes) memento public Memento SaveMemento() { Console.WriteLine( "\nSaving state --\n" ); var memento = new Memento(); return memento.Serialize( this ); } // Restores (deserializes) memento public void RestoreMemento(Memento memento) { Console.WriteLine( "\nRestoring state --\n" ); SalesProspect s = (SalesProspect)memento.Deserialize(); this .Name = s.Name; this .Phone = s.Phone; this .Budget = s.Budget; } } // "Memento" class Memento { private MemoryStream _stream = new MemoryStream(); private SoapFormatter _formatter = new SoapFormatter(); public Memento Serialize( object o) { _formatter.Serialize(_stream, o); return this ; } public object Deserialize() { _stream.Seek(0, SeekOrigin.Begin); object o = _formatter.Deserialize(_stream); _stream.Close(); return o; } } // "Caretaker" class ProspectMemory { public Memento Memento { get ; set ; } } } |
备忘录模式解说
备忘录模式有两个目标:
存储系统关键对象的重要状态。
维护关键对象的封装。
.NET中的备忘录模式
序列化是将对象转换为一个线性的字节序列从而可以存储或是传输到另一个位置的过程。反序列化是接受存储信息并用其重新创建对象的过程。备忘录模式创建对象状态的一个快照并提供将其还原到之前状态的能力。这正是.NET序列化架构所提供的,因此其有资格作为.NET Framework中备忘录模式的一个例子。
效果及实现要点
要保存的细节给封装在了Memento中,以后要更改保存的细节也不用影响客户端了。
备忘录模式比较适用于功能比较复杂的,但需要维护或记录属性的类,或者需要保存的属性只是众多属性中的一小部分时,Originator可以根据保存的Memento信息还原到前一状态。
如果在某个系统中使用命令模式时,需要实现命令的撤销功能,那么命令模式可以使用备忘录模式来存储可撤销操作的状态。
使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来。
当角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原。
将被存储的状态放在外面,不要和关键对象混在一起,可以帮助维护内聚。保持关键对象的数据封装。
在备份和恢复Memento对象时使用.NET内置的序列化机制也是很好的选择之一。
总结
通过实现备忘录模式,对象可以管理多个状态,并且很好的封装这些状态实现高内聚。也可以方便的在这些状态之间进行切换。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
2012-12-12 WPF,Silverlight与XAML读书笔记第三十三 - 可视化效果之布局内容溢出处理
2011-12-12 WPF,Silverlight与XAML读书笔记第十五 - 页间导航 页间数据传递
2008-12-12 Nibblestutotials.net教程 – Blend & Silverlight1系列之Button Basic