在平时,我们可能需要在某些时候对状态进行保存,然后可以恢复到之前的状态。比如:下棋的时候可能会出现悔棋,恢复到上一步或者更上一步的状态。再比如,文本编辑器,会有撤销的场景。再比如,玩通关游戏,关卡可能分很多小关,打一个小Boss后需要打一个大Boss,我们需要在某些阶段对游戏快照进行保存,这样可能恢复到之前的状态,继续玩耍。
这种将对象状态外部化存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态,这就是本文今天要说的备忘录模式,又叫快照模式或者Token模式。
一. 备忘录模式的基本介绍
意图
在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
结构
备忘录模式的基本结构如下:
这里涉及到的参与者有如下几种:
二. 备忘录模式的示例
接下来,我们就模拟一个游戏存档的场景来给出一个备忘录模式的示例。
package com.wangmengjun.tutorial.designpattern.memento;
public class Game {
private String description;
private int score;
private int nums;
public GameProgressMemento createMemento() {
return new GameProgressMemento(description, score, nums);
}
public void undoFromGameProgressMemento(GameProgressMemento mementor) {
this.score = mementor.getScore();
this.nums = mementor.getNums();
this.description = mementor.getDescription();
}
/**
* @return the description
*/
public String getDescription() {
return description;
}
/**
* @param description the description to set
*/
public void setDescription(String description) {
this.description = description;
}
/**
* @return the score
*/
public int getScore() {
return score;
}
/**
* @param score the score to set
*/
public void setScore(int score) {
this.score = score;
}
/**
* @return the nums
*/
public int getNums() {
return nums;
}
/**
* @param nums the nums to set
*/
public void setNums(int nums) {
this.nums = nums;
}
@Override
public String toString() {
return "Game [description(游戏简单描述)=" + description + ", score(得分)=" + score + ", nums(剩余生命数)=" + nums + "]";
}
}
package com.wangmengjun.tutorial.designpattern.memento;
import java.io.Serializable;
public class GameProgressMemento implements Serializable {
private static final long serialVersionUID = -6283136884189402182L;
private String description;
private int score;
private int nums;
public GameProgressMemento(String description, int score, int nums) {
super();
this.description = description;
this.score = score;
this.nums = nums;
}
/**
* @return the description
*/
public String getDescription() {
return description;
}
/**
* @param description the description to set
*/
public void setDescription(String description) {
this.description = description;
}
/**
* @return the score
*/
public int getScore() {
return score;
}
/**
* @param score the score to set
*/
public void setScore(int score) {
this.score = score;
}
/**
* @return the nums
*/
public int getNums() {
return nums;
}
/**
* @param nums the nums to set
*/
public void setNums(int nums) {
this.nums = nums;
}
@Override
public String toString() {
return "GameProgressMemento [description=" + description + ", score=" + score + ", nums=" + nums + "]";
}
}
package com.wangmengjun.tutorial.designpattern.memento;
import java.util.Stack;
public class GameProgressCaretaker {
private Stack<GameProgressMemento> mememtors = new Stack<>();
public void add(GameProgressMemento memento) {
mememtors.add(memento);
}
public GameProgressMemento getMemento() {
return this.mememtors.pop();
}
}
package com.wangmengjun.tutorial.designpattern.memento;
public class Client {
public static void main(String[] args) {
/**
* 1、游戏开始描述
*/
Game game = new Game();
game.setNums(3);
game.setScore(0);
game.setDescription("游戏开始");
System.out.println(game);
System.out.println();
/**
* 2、打完小Boss,保存一个备忘记录
*/
game.setNums(2);
game.setScore(15000);
game.setDescription("打掉小Boss");
System.out.println(game);
System.out.println("*******************************创建一个游戏进度备忘********************************************************");
GameProgressMemento memento = game.createMemento();
GameProgressCaretaker caretaker = new GameProgressCaretaker();
caretaker.add(memento);
/**
* 3、打不过大Boss
*/
System.out.println();
System.out.println("打大Boss剩余1条命,保存一下");
game.setNums(1);
game.setScore(36000);
game.setDescription("打大Boss剩余1条命");
System.out.println(game);
System.out.println("*******************************创建一个游戏进度备忘********************************************************");
caretaker.add(game.createMemento());
/**
* 4、打大Boss失败
*/
game.setNums(0);
game.setScore(51200);
game.setDescription("打大Boss失败,剩余0条命");
System.out.println();
System.out.println("打大Boss失败,游戏信息");
System.out.println(game);
System.out.println();
System.out.println("打大Boss发现打不过");
game.setNums(0);
game.setScore(36000);
System.out.println("打大Boss失败,游戏信息");
System.out.println(game);
/**
* 5、恢复到之前的打完小Boss的状态
*/
System.out.println();
System.out.println("*******************************恢复到上一个游戏状态********************************************************");
game.undoFromGameProgressMemento(caretaker.getMemento());
System.out.println("恢复后的游戏信息");
System.out.println(game);
System.out.println();
System.out.println("*******************************恢复到上一个游戏状态********************************************************");
game.undoFromGameProgressMemento(caretaker.getMemento());
System.out.println("恢复后的游戏信息");
System.out.println(game);
System.out.println();
}
}
输出结果:
Game [description(游戏简单描述)=游戏开始, score(得分)=0, nums(剩余生命数)=3]
Game [description(游戏简单描述)=打掉小Boss, score(得分)=15000, nums(剩余生命数)=2]
*******************************创建一个游戏进度备忘********************************************************
打大Boss剩余1条命,保存一下
Game [description(游戏简单描述)=打大Boss剩余1条命, score(得分)=36000, nums(剩余生命数)=1]
*******************************创建一个游戏进度备忘********************************************************
打大Boss失败,游戏信息
Game [description(游戏简单描述)=打大Boss失败,剩余0条命, score(得分)=51200, nums(剩余生命数)=0]
打大Boss发现打不过
打大Boss失败,游戏信息
Game [description(游戏简单描述)=打大Boss失败,剩余0条命, score(得分)=36000, nums(剩余生命数)=0]
*******************************恢复到上一个游戏状态********************************************************
恢复后的游戏信息
Game [description(游戏简单描述)=打大Boss剩余1条命, score(得分)=36000, nums(剩余生命数)=1]
*******************************恢复到上一个游戏状态********************************************************
恢复后的游戏信息
Game [description(游戏简单描述)=打掉小Boss, score(得分)=15000, nums(剩余生命数)=2]
至此一个简单的游戏状态保存和状态恢复的示例就完成了。
三. 小结
优缺点
优点:
1、有时一些发起人对象的内部信息必须保存在发起人对象以外的地方,但是必须要由发起人对象自己读取,这时,使用备忘录模式可以把复杂的发起人内部信息对其他的对象屏蔽起来,从而可以恰当地保持封装的边界。
2、本模式简化了发起人类。发起人不再需要管理和保存其内部状态的一个个版本,客户端可以自行管理他们所需要的这些状态的版本。
3、当发起人角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原。
缺点:
1、如果发起人角色的状态需要完整地存储到备忘录对象中,那么在资源消耗上面备忘录对象会很昂贵。
2、当负责人角色将一个备忘录 存储起来的时候,负责人可能并不知道这个状态会占用多大的存储空间,从而无法提醒用户一个操作是否很昂贵。
3、当发起人角色的状态改变的时候,有可能这个协议无效。如果状态改变的成功率不高的话,不如采取“假如”协议模式。
参考
[1]. 阎宏. Java与模式.电子工业出版社
[2]. Erich Gamma. 设计模式-可复用面向对象软件的基础. 机械工业出版社.