模式定义
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。
模版实现如下:
- package com.niuh.designpattern.memento.v1;
-
-
- public class MementoPattern {
- public static void main(String[] args) {
- Originator or = new Originator();
- Caretaker cr = new Caretaker();
- or.setState("S0");
- System.out.println("初始状态:" + or.getState());
- cr.setMemento(or.createMemento()); //保存状态
- or.setState("S1");
- System.out.println("新的状态:" + or.getState());
- or.restoreMemento(cr.getMemento()); //恢复状态
- System.out.println("恢复状态:" + or.getState());
- }
- }
-
- //备忘录
- class Memento {
- private String state;
-
- public Memento(String state) {
- this.state = state;
- }
-
- public void setState(String state) {
- this.state = state;
- }
-
- public String getState() {
- return state;
- }
- }
-
- //发起人
- class Originator {
- private String state;
-
- public void setState(String state) {
- this.state = state;
- }
-
- public String getState() {
- return state;
- }
-
- public Memento createMemento() {
- return new Memento(state);
- }
-
- public void restoreMemento(Memento m) {
- this.setState(m.getState());
- }
- }
-
- //管理者
- class Caretaker {
- private Memento memento;
-
- public void setMemento(Memento m) {
- memento = m;
- }
-
- public Memento getMemento() {
- return memento;
- }
- }
输出结果如下:
- 初始状态:S0
- 新的状态:S1
- 恢复状态:S0
解决的问题
备忘录模式能记录一个对象的内部状态,当用户后悔时能撤销当前操作,使数据恢复到它原先的状态。
每个人都有犯错误的时候,都希望有种“后悔药”能弥补自己的过失,让自己重新开始,但现实是残酷的。在计算机应用中,客户同样会常常犯错误,能否提供“后悔药”给他们呢?当然是可以的,而且是有必要的。这个功能由“备忘录模式”来实现。
模式组成
备忘录模式的核心是设计备忘录类以及用于管理备忘录的管理者类。
实例说明
实例概况
以游戏存档为例,看一下如何用备忘录模式实现
使用步骤
步骤1:定义备忘录角色,用于存储角色状态。
- 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 getVit() {
- return vit;
- }
-
- public void setVit(int vit) {
- this.vit = vit;
- }
-
- public int getAtk() {
- return atk;
- }
-
- public void setAtk(int atk) {
- this.atk = atk;
- }
-
- public int getDef() {
- return def;
- }
-
- public void setDef(int def) {
- this.def = def;
- }
- }
步骤2:定义发起人角色(当前游戏角色),记录当前游戏角色的生命力、攻击力、防御力。通过saveState()方法来保存当前状态,通过recoveryState()方法来恢复角色状态。
- class GameRole {
-
- private int vit; //生命力
- private int atk; //攻击力
- private int def; //防御力
-
- public int getVit() {
- return vit;
- }
-
- public void setVit(int vit) {
- this.vit = vit;
- }
-
- public int getAtk() {
- return atk;
- }
-
- public void setAtk(int atk) {
- this.atk = atk;
- }
-
- public int getDef() {
- return def;
- }
-
- public void setDef(int def) {
- this.def = def;
- }
-
- //状态显示
- public void stateDisplay() {
- System.out.println("角色当前状态:");
- System.out.println("体力:" + this.vit);
- System.out.println("攻击力:" + this.atk);
- System.out.println("防御力: " + this.def);
- System.out.println("-----------------");
- }
-
- //获得初始状态
- public void getInitState() {
- this.vit = 100;
- this.atk = 100;
- this.def = 100;
- }
-
- //战斗后
- public void fight() {
- this.vit = 0;
- this.atk = 0;
- this.def = 0;
- }
-
- //保存角色状态
- public RoleStateMemento saveState() {
- return (new RoleStateMemento(vit, atk, def));
- }
-
- //恢复角色状态
- public void recoveryState(RoleStateMemento memento) {
- this.vit = memento.getVit();
- this.atk = memento.getAtk();
- this.def = memento.getDef();
- }
- }
步骤3:定义管理者角色,角色状态管理者
- class RoleStateCaretaker {
-
- private RoleStateMemento memento;
-
- public RoleStateMemento getMemento() {
- return memento;
- }
-
- public void setMemento(RoleStateMemento memento) {
- this.memento = memento;
- }
- }
步骤4:测试输出
- public class MementoPattern {
-
- // 逻辑大致为打boss前存档,打boss失败了
- public static void main(String[] args) {
- //打boss前
- GameRole gameRole = new GameRole();
- gameRole.getInitState();
- gameRole.stateDisplay();
-
- //保存进度
- RoleStateCaretaker caretaker = new RoleStateCaretaker();
- caretaker.setMemento(gameRole.saveState());
-
- //打boss失败
- gameRole.fight();
- gameRole.stateDisplay();
-
- //恢复状态
- gameRole.recoveryState(caretaker.getMemento());
- gameRole.stateDisplay();
- }
- }
输出结果
- 角色当前状态:
- 体力:100
- 攻击力:100
- 防御力: 100
- -----------------
- 角色当前状态:
- 体力:0
- 攻击力:0
- 防御力: 0
- -----------------
- 角色当前状态:
- 体力:100
- 攻击力:100
- 防御力: 100
优点
备忘录模式是一种对象行为型模式,其主要优点如下。
- 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
- 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
- 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
缺点
资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
注意事项
- 为了符合迪米特法则,需要有一个管理备忘录的类
- 不要在频繁建立备份的场景中使用备忘录模式。为了节约内存,可使用原型模式+备忘录模式
应用场景
- 需要保存和恢复数据的相关场景
- 提供一个可回滚的操作,如ctrl+z、浏览器回退按钮、Backspace键等
- 需要监控的副本场景
模式的扩展
在备忘录模式中,有单状态备份的例子,也有多状态备份的例子。可以结合原型模式混合使用。在备忘录模式中,通过定义“备忘录”来备份“发起人”的信息,而原型模式的 clone() 方法具有自备份功能,所以,如果让发起人实现 Cloneable 接口就有备份自己的功能,这时可以删除备忘录类,其结构如下:
源码中的应用
- #Spring
- org.springframework.binding.message.StateManageableMessageContext
StateManageableMessageContext 部分源码
- public interface StateManageableMessageContext extends MessageContext {
-
-
- public Serializable createMessagesMemento();
-
-
- public void restoreMessages(Serializable messagesMemento);
-
-
- public void setMessageSource(MessageSource messageSource);
- }
PS:以上代码提交在 Github :
https://github.com/Niuh-Study/niuh-designpatterns.git