视频链接:
https://www.bilibili.com/video/BV1ZK411V7gS/
1定义
备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
备忘录模式结构图
Originator (发起人):负责创建一个备忘录Memento, 用以记录当前时刻它的内部状态,并可使用备忘录恢复内部状态。Originator 可根据需要决定Memento存储Originator的哪些内部状态。
Memento (备忘录):负责存储Originator对象的内部状态,并可防止Originator以外的其他对象访问备忘录Memento。备忘录有两个接口,Caretaker 只能看到备忘录的窄接口,它只能将备忘录传递给其他对象。Originator 能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。
Caretaker (管理者):负责保存好备忘录Memento,不能对备忘录的内容进行操作或检查。
2意义与作用
备忘录模式基本代码:
发起人(Originator) 类
class Originator { private string state; public string State { get{ return state; } get{ state = value; } } public Memento CreateMemento () { return (new Memento (state)) ; } public void SetMemento (Memento memento) { state = memento.State; } public void Show () { Console. WriteLine ("State=” + state) ; } }
备忘录(Memento)类
class Memento { private string state; public Memento (string state) { this.state = state; } public string State { get { return state; } } }
管理者(Caretaker) 类
class Caretaker { private Memento memento; public Memento Memento { get{ return memento;} set { memento = value;} } }
客户端程序
static void Main(string[] args) { Originator o= new Originator(); o.State = "On"; o.Show(); Caretaker c = new Caretaker() ; c.Memento = o. Creat eMemento() ; o.State = "Off"; o.Show(); o. SetMemento (c.Memento) ; o.Show() ; Console.Read() ; }
在备忘录模式中,把要保存的细节给封装在了Memento中了,如果要更改保存的细节也不会影响客户端。
Memento模式比较适用于功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分时, Originator可以根据保存的Memento信息还原到前一状态。
如果在某个系统中使用命令模式时,需要实现命令的撤销功能,那么命令模式可以使用备忘录模式来存储可撤销操作的状态。有时一些对象的内部信息必须保存在对象以外的地方,但是必须要由对象自己读取,这时,使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来,从而可以恰当地保持封装的边界。
备忘录模式最大的作用还是在当角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原。
所以总结下来就是:
1. 它提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。
2. 备忘录实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。备忘录保存了原发器的状态,采用列表、堆栈等集合来存储备忘录对象可以实现多次撤销操作。
3应用场景
备忘录模式被使用于以下的情况:
1. 需要保存一个对象在某一个时刻的状态或部分状态。
2. 如果用一个接口来让其他对象得到这些状态,将会暴露对象的实现细节并破坏对象的封装性,一个对象不希望外界直接访问其内部状态,通过中间对象可以间接访问其内部状态。
所以备忘录模式经常可以遇到,譬如下面这些场景:
- 浏览器回退:浏览器一般有浏览记录,当我们在一个网页上点击几次链接之后,可在左上角点击左箭头回退到上一次的页面,然后也可以点击右箭头重新回到当前页面
- 数据库备份与还原:一般的数据库都支持备份与还原操作,备份即将当前已有的数据或者记录保留,还原即将已经保留的数据恢复到对应的表中。
- 编辑器撤销与重做:在编辑器上编辑文字,写错时可以按快捷键 Ctrl + z 撤销,撤销后可以按 Ctrl + y 重做。
- 虚拟机生成快照与恢复:虚拟机可以生成一个快照,当虚拟机发生错误时可以恢复到快照的样子。
- Git版本管理:Git是最常见的版本管理软件,每提交一个新版本,实际上Git就会把它们自动串成一条时间线,每个版本都有一个版本号,使用 git reset --hard 版本号 即可回到指定的版本,让代码时空穿梭回到过去某个历史时刻。
- 棋牌游戏悔棋:在棋牌游戏中,有时下快了可以悔棋,回退到上一步重新下。
4实例实现
游戏进度备忘录结构图
游戏角色类:1. 新增“保存角色状态”方法,将游戏角色的三个状态值通过实例化“角色状态存储箱”返回。2. 新增“恢复角色状态”方法,可将外部的“角色状态存储箱”中状态值恢复给游戏角色。
class 游戏角色 { …… //保存角色状态 public RoleStateMemento SaveState () { return (new RoleStateMemento(vit, atk, def) ) ; } //恢复角色状态 public void RecoveryState (RoleStateMemento memento) { this.vit = memento. Vitality; this.atk = memento. Attack; this.def = memento. Defense; } …… }
角色状态存储箱类:将生命力、攻击力、防御力存入状态存储箱对象中。
//角色状态存储箱 class RoleStateMemento { private int vit; private int atk; private int def; public RoleStateMemento(int vit, int atk, int def) { this.vit = vit; this.atk = atk; this.def = def; } //生命力 public int Vitality { get { return vit; } set { vit = value; } } //攻击力 public int Attack { get { return atk;} set{ atk = value;} } //防御力 public int Defense { get { return def; } set { def = value; } } }
角色状态管理者类
//角色状态管理者 class RoleStateCaretaker { private RoleStateMemento memento ; public RoleStateMemento Memento { get{ return memento;} set { memento = value;} } }
客户端代码:1. 游戏角色初始状态,三项指标数据都是100。2. 保存进度时,由于封装在Memento中,因此我们并不知道保存了哪些具体的角色数据。
static void Main(string[] args) { //大战Boss之前 GameRole lixiaoyao = new GameRole() ; lixiaoyao.GetInitState() ; lixiaoyao.StateDisplay() ; //保存进度 RoleStateCaretaker stateAdmin = new RoleStateCaretaker () ; stateAdmin. Memento = lixiaoyao.SaveState() ; //大战Boss时,损耗严重 lixiaoyao.Fight() ; lixiaoyao.StateDisplay() ; //恢复之前状态 Lixiaoyao.RecoveryState (stateAdmin . Memento) ; lixiaoyao.StateDisplay() ; Console.Read() ; }
5总结
备忘录模式是一种对象行为型模式,其主要优点如下:
- 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
- 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
- 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
其主要缺点是:资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
参考链接:
https://blog.csdn.net/wwwdc1012/article/details/83353580
https://www.jianshu.com/p/14d27c8fea8d