引言
我们在编程的时候,经常需要保存对象的中间状态,当需要的时候,可以恢复到这个状态。比如我们使用Word进行文档编辑时,不小心误删除了一些内容或者改变了内容格式,我们希望回滚编辑前的状态,便可以使用Ctrl+Z依次取消操作来进行恢复,此时其实就是备忘录模式就是解决这类问题的最佳选项。附行为型设计模式系列文章列表:
- 设计模式——行为型之使用模板方法(Template Method Pattern)模式尽量减少重复相似的代码段(一)
- 设计模式——行为型模式之通过中介者模式(Mediator Pattern)实现各模块之间的解耦(二)
- 设计模式——行为型模式之借助策略模式(Strategy Pattern)减少使用不必要的if-else if -else和switch-case(三)
- 设计模式——行为型设计模之借助观察者模式(Observer Pattern)实现模块之间的解耦(四)
- 设计模式——行为型模式之借助责任链模式(Chain of Responsibility)灵活完成链式处理(五)
- 设计模式——行为型之命令模式(Command Pattern)让你的命令发起者和命令执行者的逻辑解耦(六)
- 设计模式——行为型之使用备忘录模式(Memento Pattern)随时回滚状态(七)
一、备忘录模式概述
备忘录模式(Memento Pattern) 是一种比较简单的行为型模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态(Without violating encapsulation,capture and externalize an object’s internal state so that the object can be restored to this state later)。简而言之,在不破坏封装性的前提下,捕获一个对象的内部状态,并将其保存在另外一个对象之上,以便将该对象恢复到保存前的状态,有点像编程世界中的“后悔药”,通过这个模式可以方便地回滚被捕获保存的状态,通常标准的备忘录模式的主要参与角色有:Originator发起人角色、Memento备忘录角色、Caretaker备忘录管理员角色。
二、备忘录模式的优点和缺点及可用场景
1、备忘录模式的优点
给用户提供一种可以回滚回复状态的机制,使用户可以比较方便的回滚到历史版本的某个状态
实现了信息的封装,用户不需要关心状态的保存细节。
2、备忘录模式的缺点
- 考虑到消耗资源,若类的成员变量过多势必会占用比较大的资源,而且每次保存都会消耗一定的资源。
3、备忘录模式的可用场景及注意事项
需要保存和恢复数据的相关状态场景。
如果需要把自身对象的状态开放出去给其他对象访问时,通过提供一个接口方式会暴露对象的实现细节并破坏对象的封装性,所以当不希望外接直接访问这些内部状态时,可以通过备忘录模式借助中间对象访问其内部状态。
提供一个可回滚(rollback)的操作,比如Word中的CTRL+Z组合键,IE浏览器中的后退按钮,文件管理器上的backspace键等。
需要监控的副本场景中。例如要监控一个对象的属性,但是监控又不应该作为系统的主业务来调用,它只是边缘应用,即使出现监控不准、错误报警也影响不大,因此一般的做
法是备份一个主线程中的对象,然后由分析程序来分析。数据库连接的事务管理就是用的备忘录模式
需要控制备忘录的生命期,通常备忘录创建出来就要在“最近”的代码中使用,要主动管理它的生命周期,建立就要使
用,不使用就要立刻删除其引用,等待垃圾回收器对它的回收处理。- 兼顾备忘录的性能,不要在频繁建立备份的场景中使用备忘录模式(比如一个for循环中)因为一方面是控制不了备忘录建立的对象数量,再者是大对象的建立是要消耗资源的,系统的性能需要考虑。
三、备忘录模式模式的实现
备忘录模式就是一个对象的备份模式,提供了一种程序数据的备份方法,以便随时回滚状态。
Originator发起人角色——首先是一个自身具有多种状态且需要把对这些状态的访问开放出去的对象,在备忘录模式中是核心,所谓发起人就是决定了备忘录角色可以存储哪些状态,负责创建备忘录对象(相当于是把自己的状态中转到备忘录对象中),还原自身状态(其实就是从对应的备忘录对象中获取之前保存的状态再设置到自身上)
Memento备忘录角色——本质其实就是一个普通JavaBean,可以看成是Originator角色对象状态成员部分的一个副本,备忘录模式的思想体现所在,用于存储Originator的状态,但是自身不应该具有主动存储或者管理存储的逻辑(交由Caretaker备忘录管理员角色负责)
Caretaker备忘录管理员角色——负责对Memento备忘录角色进行建档存档,但是不应该有对备忘录的内容进行访问或修改操作,只是作为一个把备忘录传递出去给Originator。
1、实现Originator角色
package statusofcls.memoto;
//以吃鸡游戏为例,简单模拟游戏过程对于玩家各种状态的存储和还原(真实游戏模型肯定不是这样)
public class RiskGaming {
private String weapon;
private String equip;
private int life;
private int goals;
//自身的业务逻辑
public void play(){
System.out.println("努力吃鸡中... 进入仓库拿到 AK 一把,平底锅,棒球帽一顶");
weapon="装备上武器:AK";
equip="装备防具上:平底锅,棒球帽";
System.out.println("被狙点了一枪,剩余生命值60");
life=60;
System.out.println("蛇形走位,冲上去点射干掉一枚小菜鸡");
goals+=1;
}
public void networkErro(){
System.out.println("突然掉线了...");
System.out.println("掉线前状态..."+this.toString());
System.out.println("---------------------------------------------------");
}
//创建备忘录
public GameMemoto createMemoto(){
GameMemoto memoto=new GameMemoto();
memoto.setWeapon(weapon);
memoto.setEquip(equip);
memoto.setLife(life);
memoto.setGoals(goals);
return memoto;
}
//通过备忘录恢复保存的状态
public void restore(GameMemoto memoto){
this.weapon=memoto.getWeapon();
this.equip=memoto.getEquip();
this.life=memoto.getLife();
this.goals=memoto.getGoals();
System.out.println("通过备忘录恢复状态..."+this.toString());
}
@Override
public String toString() {
return "RiskGaming [weapon=" + weapon + ", equip=" + equip + ", life="
+ life + ", goals=" + goals + "]";
}
}
2、Memento备忘录角色
package statusofcls.memoto;
/**
* 所谓的备忘录角色 ,本质上就是一个用于保存Originator角色状态的javaBean
* @author Crazy.Mo
*
*/
public class GameMemoto {
private String weapon;
private String equip;
private int life;
private int goals;
public String getWeapon() {
return weapon;
}
public void setWeapon(String weapon) {
this.weapon = weapon;
}
public String getEquip() {
return equip;
}
public void setEquip(String equip) {
this.equip = equip;
}
public int getLife() {
return life;
}
public void setLife(int life) {
this.life = life;
}
public int getGoals() {
return goals;
}
public void setGoals(int goals) {
this.goals = goals;
}
@Override
public String toString() {
return "GameMemoto [weapon=" + weapon + ", equip=" + equip + ", life="
+ life + ", goals=" + goals + "]";
}
}
3、Caretaker备忘录管理员角色
package statusofcls.memoto;
//Caretaker 角色主要是负责管理备忘录角色的
public class MemotoManager {
GameMemoto memoto;
public void archive(GameMemoto memoto) {
this.memoto = memoto;
}
public GameMemoto getMemoto() {
return memoto;
}
}
测试
package statusofcls.memoto;
public class Client {
public static void main(String[] args) {
RiskGaming gaming=new RiskGaming();//创建原始的Originator角色
gaming.play();//Originator角色因业务导致自身的状态发生改变
MemotoManager manager=new MemotoManager();//创建Caretaker角色
GameMemoto memoto=gaming.createMemoto();//Originator角色主动完成创建备忘录
manager.archive(memoto);//通过Caretaker 完成备忘录的存档
gaming.networkErro();//假如说突然掉线了,重连回来肯定得对当时的状态进行恢复
RiskGaming gaming2=new RiskGaming();//再创建原始的Originator角色
gaming2.restore(manager.getMemoto());
}
}
小结
在上面简单模拟游戏的例子中,RiskGaming作为所谓的Originator发起人角色,除了自身的逻辑外,还必须实现创建备忘录角色和通过备忘录恢复自身状态的逻辑,然后在使用的过程中客户端首先创建Originator发起人角色,然后当自身的状态发生变化的时候再中转到备忘录角色中,而要中转存储首先得由发起人角色主动建立备忘录角色,再由Caretaker备忘录管理员角色完成对备忘录的建档转发,最后Originator发起人再通过Caretaker备忘录管理员角色访问备忘录并恢复对应的状态。虽然看起来备忘录像是把原本简单的逻辑变得复杂,但在某些情况下并不多余,虽然角色较多但每一个角色都职责分明,责任单一,但是更重要的是屏蔽了对Originator角色的直接访问,不破坏对象自身的封装。