备忘录模式
1 备忘录模式介绍
备忘录模式提供了一种对象状态的撤销实现机制,当系统中某一个对象需要恢复到某一历史状态时可以使用备忘录模式进行设计.
很多软件都提供了撤销(Undo)操作,如 Word、记事本、Photoshop、IDEA等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 浏览器 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
备忘录模式(memento pattern)定义: 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态.
2 备忘录模式原理
备忘录模式的主要角色如下:
- 发起人(Originator)角色:状态需要被记录的元对象类, 记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
- 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- 看护人(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
3 备忘录模式实现
下面我们再来看看 UML 对应的代码实现。首先,我们创建原始对象 Originator,对象中有四个属性,分别是 state 用于显示当前对象状态,id、name、phone 用来模拟业务属性,并添加 get、set 方法、create() 方法用于创建备份对象,restore(memento) 用于恢复对象状态。
/*** 发起人类**/
public class Originator {private String state = "原始对象";private String id;private String name;private String phone;public Originator() {}//创建备忘录对象public Memento create(){return new Memento(id,name,phone);}//恢复对象状态public void restore(Memento m){this.state = m.getState();this.id = m.getId();this.name = m.getName();this.phone = m.getPhone();}public String getState() {return state;}public void setState(String state) {this.state = state;}public String getId() {return id;}public void setId(String id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}@Overridepublic String toString() {return "Originator{" +"state='" + state + '\'' +", id='" + id + '\'' +", name='" + name + '\'' +", phone='" + phone + '\'' +'}';}
}/*** 备忘录对象* 访问权限为: 默认,也就是同包下可见(保证只有发起者类可以访问备忘录类)**/
class Memento {private String state = "从备份对象恢复为原始对象";private String id;private String name;private String phone;public Memento() {}public Memento(String id, String name, String phone) {this.id = id;this.name = name;this.phone = phone;}//get、set、toString......
}/*** 负责人类-保存备忘录对象**/
public class Caretaker {private Memento memento;public Memento getMemento() {return memento;}public void setMemento(Memento memento) {this.memento = memento;}
}public class Client {public static void main(String[] args) {//创建发起人对象Originator originator = new Originator();originator.setId("1");originator.setName("spike");originator.setPhone("13512341234");System.out.println("=============" + originator);//创建负责人对象,并保存备忘录对象Caretaker caretaker = new Caretaker();caretaker.setMemento(originator.create());//修改originator.setName("update");System.out.println("=============" + originator);//从负责人对象中获取备忘录对象,实现撤销originator.restore(caretaker.getMemento());System.out.println("=============" + originator);}
}
4 备忘录模式应用实例
设计一个收集水果和获取金钱数的掷骰子游戏,游戏规则如下
- 游戏玩家通过扔骰子来决定下一个状态
- 当点数为1,玩家金钱增加
- 当点数为2,玩家金钱减少
- 当点数为6,玩家会得到水果
- 当钱消耗到一定程度,就恢复到初始状态
- Memento类: 表示玩家的状态
/*** Memento 表示状态**/
public class Memento {int money; //所持金钱ArrayList fruits; //获得的水果//构造函数Memento(int money) {this.money = money;this.fruits = new ArrayList();}//获取当前玩家所有的金钱int getMoney() {return money;}//获取当前玩家所有的水果List getFruits() {return (List)fruits.clone();}//添加水果void addFruit(String fruit){fruits.add(fruit);}
}
- Player玩家类,只要玩家的金币还够,就会一直进行游戏,在该类中会设置一个createMemento方法,其作用是保存当前玩家状态.还会包含一个restore撤销方法,相当于复活操作.
package com.mashibing.memento.example02;import java.util.ArrayList;
import java.util.List;
import java.util.Random;public class Player {private int money; //所持金钱private List<String> fruits = new ArrayList(); //获得的水果private Random random = new Random(); //随机数对象private static String[] fruitsName={ //表示水果种类的数组"苹果","葡萄","香蕉","橘子"};//构造方法public Player(int money) {this.money = money;}//获取当前所持有的金钱public int getMoney() {return money;}//获取一个水果public String getFruit() {String prefix = "";if (random.nextBoolean()) {prefix = "好吃的";}//从数组中获取水果String f = fruitsName[random.nextInt(fruitsName.length)];return prefix + f;}//掷骰子游戏public void yacht(){int dice = random.nextInt(6) + 1; //掷骰子if(dice == 1){money += 100;System.out.println("所持有的金钱增加了..");}else if(dice == 2){money /= 2;System.out.println("所持有的金钱减半..");}else if(dice == 6){ //获取水果String fruit = getFruit();System.out.println("获得了水果: " + fruit);fruits.add(fruit);}else{//骰子结果为3、4、5System.out.println("无效数字,继续投掷");}}//拍摄快照public Memento createMemento(){Memento memento = new Memento(money);for (String fruit : fruits) {if(fruit.startsWith("好吃的")){memento.addFruit(fruit);}}return memento;}//撤销方法public void restore(Memento memento){this.money = memento.money;this.fruits = memento.getFruits();}@Overridepublic String toString() {return "Player{" +"money=" + money +", fruits=" + fruits +'}';}
}
- 测试: 由于引入了备忘录模式,可以保存某个时间点的玩家状态,这样就可以对玩家进行复活操作.
public class MainApp {public static void main(String[] args) throws InterruptedException {Player player = new Player(100); //最初所持的金钱数Memento memento = player.createMemento(); //保存最初状态for (int i = 0; i < 100; i++) {//显示扔骰子的次数System.out.println("=====" + i);//显示当前状态System.out.println("当前状态: " + player);//开启游戏player.yacht();System.out.println("所持有的金钱为: " + player.getMoney() + " 元");//决定如何操作Mementoif(player.getMoney() > memento.getMoney()){System.out.println("赚到金币,保存当前状态,继续游戏!");memento = player.createMemento();}else if(player.getMoney() < memento.getMoney() / 2){System.out.println("所持金币不多了,将游戏恢复到初始状态!");player.restore(memento);}Thread.sleep(1000);System.out.println("");}}
}
5 备忘录模式总结
1 备忘录模式的优点
- 提供了一种状态恢复的实现机制,使得用户可以方便的回到一个特定的历史步骤,当新的状态无效或者存在问题的时候,可以使用暂时存储起来的备忘录将状态复原.
- 备忘录实现了对信息的封装,一个备忘录对象是一种发起者对象状态的表示,不会被其他代码所改动.备忘录保存了发起者的状态,采用集合来存储备忘录可以实现多次撤销的操作
2 备忘录模式的缺点
- 资源消耗过大,如果需要保存的发起者类的成员变量比较多, 就不可避免的需要占用大量的存储空间,每保存一次对象的状态,都需要消耗一定系统资源
3 备忘录模式使用场景
- 需要保存一个对象在某一时刻的状态时,可以使用备忘录模式.
- 不希望外界直接访问对象内部状态时.