备忘录模式(Memento Pattern)
备忘录模式在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到之前保存的状态。大类划分为行为型模式。
使用备忘录模式可以避免暴漏原有的发起者对象的内部信息,而该信息又需要保存在外部。备忘录模式把可能很复杂的对象内部信息对其他对象封闭起来,从而保证了发起者封装的边界,有效避免了因保存在外部数据的修改而对发起者对象内部造成影响。
介绍
意图:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
角色:
1. 发起人角色(Originator):发起人角色在要存储当前状态时,产生一个备忘录;在需要恢复之前的状态时,则接收外部备忘录对象用来恢复状态。
2. 备忘录角色(Memento):备忘录角色用来保存某一时刻发起者对象的内部状态信息,但是需要保证不能将其保存的信息泄露给其他对象。
3. 管理者角色(CareTaker):管理者角色用来保存发起者对象产生的备忘录实例,然后在一个合适的时机,发起者通知管理者,获得备忘录对象实例恢复之前的状态。
使用场景:
1. 需要在某一时刻恢复一个对象先前的状态时;
2. 需要在外部保存对象某一时刻的状态,但如果用一个接口来让其他对象直接获得这些状态,将会暴漏对象的实现细节并破坏对象的封装性。
优点:
1. 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
2. 实现了信息的封装,使得用户不需要关心状态的保存细节。
缺点:消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
注意事项:
1. 为了符合迪米特原则,还要增加一个管理备忘录的类。
2. 为了节约内存,可使用原型模式+备忘录模式。
实现
本例中,我们模拟一个Hero挑战Boss的游戏场景,Hero有三次机会挑战Boss,如果三次挑战不成功,则恢复到上个状态。
步骤 1
我们先定义一个备忘录接口,这个接口主要的目的是为了系统安全。
INarrowMemento.java
package com.mrcsj.test.study.memento;
/**
* @author admin
* @version 1.0
* @created 22-十一月-2016 13:40:01
*/
public interface INarrowMemento {
}
步骤 2
定义一个备忘录管理者,用来存储Hero对象的初始状态。
CareTaker.java
package com.mrcsj.test.study.memento;
/**
* 备忘录管理者
* @author admin
* @version 1.0
* @created 22-十一月-2016 13:39:54
*/
public class CareTaker {
private INarrowMemento memento;
public CareTaker(){
}
public void finalize() throws Throwable {
}
public INarrowMemento getMemento(){
return memento;
}
/**
*
* @param memento
*/
public void setMemento(INarrowMemento memento){
this.memento = memento;
}
}//end CareTaker
步骤 3
定义Hero对象,内部有一个实现了INarrowMemento的内部类,该内部类即是备忘录。
Hero.java
package com.mrcsj.test.study.memento;
import java.util.Random;
/**
* @author admin
* @version 1.0
* @created 22-十一月-2016 13:39:58
*/
public class Hero {
private int blood;
private int sword;
public Hero(){
this.blood = 100;
this.sword = 100;
}
public void finalize() throws Throwable {
}
public INarrowMemento CreateMemento(){
System.out.println("create memento");
return new Memento(this.blood, this.sword);
}
/**
* 挑战Boss
* @return
*/
public int koBoss(){
if(this.blood <= 0 || this.sword <= 0) {
System.out.println("挑战失败");
return -1;
}else {
double win = Math.random();
if(win <= 0.02) {
System.out.println(this.toString());
System.out.println("恭喜你挑战成功!");
return 1;
}else {
Random random = new Random();
System.out.println(this.toString());
System.out.println("继续攻击boss");
int blood_sub = random.nextInt(10);
int sword_sub = random.nextInt(10);
this.blood -= blood_sub;
this.sword -= sword_sub;
return 0;
}
}
}
@Override
public String toString() {
return "当前血液:" + this.blood + ", 当前武力值:" + this.sword;
}
/**
*
* @param memento
*/
public void restoreFromMemento(INarrowMemento memento){
System.out.println("恢复备忘录中的状态");
if(memento != null) {
Memento memTmp = (Memento) memento;
this.blood = memTmp.getBlood();
this.sword = memTmp.getSword();
}
}
private class Memento implements INarrowMemento {
private int blood;
private int sword;
private Memento(int blood, int sword) {
this.blood = blood;
this.sword = sword;
}
private int getBlood() {
return blood;
}
private int getSword() {
return sword;
}
}
}//end Hero
步骤 4
建立客户端进行测试。
Client.java
package com.mrcsj.test.study.memento;
/**
* @author admin
* @version 1.0
* @created 22-十一月-2016 13:39:56
*/
public class Client {
public static void main(String[] args) {
Hero hero = new Hero();
CareTaker careTaker = new CareTaker();
careTaker.setMemento(hero.CreateMemento());
int cnt = 1;
int ko = -1;
while(ko != 1 && cnt <= 3) {
System.out.println("第" + cnt + "次挑战");
ko = hero.koBoss();
while(true) {
if(ko == -1) {
//挑战失败,恢复到初始状态,累加挑战次数
hero.restoreFromMemento(careTaker.getMemento());
cnt += 1;
break;
} else if (ko == 0) {
//继续挑战
ko = hero.koBoss();
} else if (ko == 1) {
break;
}
}
}
}
}//end Client
步骤 5
验证输出。
create memento
第1次挑战
当前血液:100, 当前武力值:100
继续攻击boss
当前血液:93, 当前武力值:93
继续攻击boss
当前血液:87, 当前武力值:91
继续攻击boss
当前血液:87, 当前武力值:86
继续攻击boss
当前血液:86, 当前武力值:81
继续攻击boss
当前血液:80, 当前武力值:74
继续攻击boss
当前血液:72, 当前武力值:70
继续攻击boss
当前血液:66, 当前武力值:61
继续攻击boss
当前血液:64, 当前武力值:57
继续攻击boss
当前血液:56, 当前武力值:53
继续攻击boss
当前血液:47, 当前武力值:50
继续攻击boss
当前血液:47, 当前武力值:44
继续攻击boss
当前血液:39, 当前武力值:41
继续攻击boss
当前血液:33, 当前武力值:39
继续攻击boss
当前血液:28, 当前武力值:34
继续攻击boss
当前血液:28, 当前武力值:27
继续攻击boss
当前血液:22, 当前武力值:23
继续攻击boss
当前血液:15, 当前武力值:20
继续攻击boss
当前血液:8, 当前武力值:15
继续攻击boss
当前血液:6, 当前武力值:11
继续攻击boss
当前血液:6, 当前武力值:10
继续攻击boss
挑战失败
恢复备忘录中的状态
第2次挑战
当前血液:100, 当前武力值:100
继续攻击boss
当前血液:92, 当前武力值:99
继续攻击boss
当前血液:89, 当前武力值:96
继续攻击boss
当前血液:81, 当前武力值:95
继续攻击boss
当前血液:76, 当前武力值:86
继续攻击boss
当前血液:72, 当前武力值:86
继续攻击boss
当前血液:65, 当前武力值:79
继续攻击boss
当前血液:63, 当前武力值:73
继续攻击boss
当前血液:58, 当前武力值:64
继续攻击boss
当前血液:51, 当前武力值:64
恭喜你挑战成功!