一、前言
备忘录模式用于保存和恢复对象的状态,相信大家看过我前面的拙作就会想到原型模式也能保存一个对象在某一个时刻的状态,那么两者有何不同的呢?原型模式保存的是当前对象的所有状态信息,恢复的时候会生成与保存的对象完全相同的另外一个实例;而备忘录模式保存的是我们关心的在恢复时需要的对象的部分状态信息,相当于快照。备忘录模式大家肯定都见过,比如在玩游戏的时候有一个保存当前闯关的状态的功能,会对当前用户所处的状态进行保存,当用户闯关失败或者需要从快照的地方开始的时候,就能读取当时保存的状态完整地恢复到当时的环境,这一点和VMware上面的快照功能很类似。
二、代码
Memento类:
package zyr.dp.memento;
import java.util.ArrayList;
import java.util.List;
public class Memento {
private int menoy;
private ArrayList fruits;
//窄接口,访问部分信息
public int getMenoy(){
return menoy;
}
//宽接口,本包之内皆可访问
Memento(int menoy){
this.menoy=menoy;
fruits=new ArrayList();//每次调用的时候重新生成,很重要
}
//宽接口,本包之内皆可访问
List getFruits(){
return (List) fruits.clone();
}
//宽接口,本包之内皆可访问
void addFruits(String fruit){
fruits.add(fruit);
}
}
Gamer类:
package zyr.dp.memento;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
public class Gamer {
private static String[] FruitsSame={"香蕉","苹果","橘子","柚子"};
private int menoy;
private List fruits=new ArrayList();
private Random random=new Random();
public int getMenoy(){
return menoy;
}
public Gamer(int menoy){
this.menoy=menoy;
}
public void bet(){
int next=random.nextInt(6)+1;
if(next==1){
menoy+=100;
System.out.println("金钱增加了100,当前金钱为:"+menoy);
}else if(next==2){
menoy/=2;
System.out.println("金钱减少了一半,当前金钱为:"+menoy);
}else if(next==6){
String f=getFruit();
fruits.add(f);
System.out.println("获得了水果:"+f+",当前金钱为:"+menoy);
}else {
System.out.println("金钱没有发生改变,当前金钱为:"+menoy);
}
}
private String getFruit() {
String prefix="";
if(random.nextBoolean()){
prefix="好吃的";
}
return prefix+FruitsSame[random.nextInt(FruitsSame.length)];
}
public Memento createMemento(){
Memento m=new Memento(menoy);
Iterator it=fruits.iterator();
while(it.hasNext()){
String fruit=(String)it.next();
if(fruit.startsWith("好吃的")){
m.addFruits(fruit);
}
}
return m;
}
public void restoreMemento(Memento memento){
this.menoy=memento.getMenoy();
this.fruits=memento.getFruits();
}
public String toString(){
return "Menoy:"+menoy+" ,Fruits:"+fruits;
}
}
Main类:
package zyr.dp.test;
import zyr.dp.memento.Gamer;
import zyr.dp.memento.Memento;
public class Main {
public static void main(String[] args) {
Gamer gamer=new Gamer(100);
Memento memento=gamer.createMemento();
for(int i=0;i<100;i++){
System.out.println("当前状态:"+i);
System.out.println("当前金额:"+gamer.getMenoy());
gamer.bet();
if(gamer.getMenoy()<memento.getMenoy()/2){
System.out.println("金钱过少,恢复到以前的状态:");
gamer.restoreMemento(memento);
System.out.println("此时状态为:"+gamer);
}else if(gamer.getMenoy()>memento.getMenoy()){
System.out.println("金钱增多,保存当前状态:");
memento=gamer.createMemento();
System.out.println("此时状态为:"+gamer);
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
本程序的功能是根据循环次数随机的生成1~6这6个数字,如果数字是1,则金钱加一百,如果是二,则金钱减半,如果是6,则随机生成水果,水果分为好吃的和不好吃的,在保存的时候只保存好吃的水果,恢复的时候就只有好吃的水果了。当金钱少于当前备忘录中金钱的一半的时候就要恢复到备忘录的状态;当金钱大于备忘录的状态的时候就要备份当前的状态,备份的时候只备份好的水果以及当前金额,这就是游戏的功能,可以看到运行的结果的正确性。
这里有几点要注意:、
1、窄接口和宽接口
在代码中我已经标注出了窄接口和宽接口,如何定义这两种接口还要看这两种接口前面的修饰符,如果是默认的(只有本包的类可以使用),并且这些接口结合到一起可以完全的将本类的信息显示出来,那么就是宽接口;只能在本包之中使用,如果修饰符是public的接口,并且只能表示本类一部分信息,因为是public可以在其他包中使用的,就是窄接口,只能查看部分信息,因此是窄的。如下图所示,对于Main类所在的包,只能使用其他两个类中声明为public的字段和方法,因此在Main中只能使用窄接口来完成一定信息的读取getMenoy()。这只是一个概念,强调的是类、字段、方法的可见性。
2、可见性
同时我们也知道,public修饰的字段和方法在任何包中都可以使用,private修饰的字段和方法只能在本类之中使用,protected修饰的方法可以在本包之中以及该类的子类(可以在其他包)中使用,默认的没有任何修饰的可以在本包之中使用。这就是四种修饰关键字的可见性。在编程的时候我们一定要考虑这些问题,不然就会导致我们不想看到的字段、方法、类被误用的结果。
3、将备份内容存盘并且读取
Memento类:
package zyr.dp.serializable;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class Memento implements Serializable {
private static final long serialVersionUID = 8497203738547925495L;
private int menoy;
private ArrayList fruits;
//窄接口,访问部分信息
public int getMenoy(){
return menoy;
}
//宽接口,本包之内皆可访问
Memento(int menoy){
this.menoy=menoy;
fruits=new ArrayList();//每次调用的时候重新生成,很重要
}
//宽接口,本包之内皆可访问
List getFruits(){
return (List) fruits.clone();
}
//宽接口,本包之内皆可访问
void addFruits(String fruit){
fruits.add(fruit);
}
}
Gamer类:
package zyr.dp.serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
public class Gamer {
private static String[] FruitsSame={"香蕉","苹果","橘子","柚子"};
private int menoy;
private List fruits=new ArrayList();
private Random random=new Random();
public int getMenoy(){
return menoy;
}
public Gamer(int menoy){
this.menoy=menoy;
}
public void bet(){
int next=random.nextInt(6)+1;
if(next==1){
menoy+=100;
System.out.println("金钱增加了100,当前金钱为:"+menoy);
}else if(next==2){
menoy/=2;
System.out.println("金钱减少了一半,当前金钱为:"+menoy);
}else if(next==6){
String f=getFruit();
fruits.add(f);
System.out.println("获得了水果:"+f+",当前金钱为:"+menoy);
}else {
System.out.println("金钱没有发生改变,当前金钱为:"+menoy);
}
}
private String getFruit() {
String prefix="";
if(random.nextBoolean()){
prefix="好吃的";
}
return prefix+FruitsSame[random.nextInt(FruitsSame.length)];
}
public Memento createMemento(){
Memento m=new Memento(menoy);
Iterator it=fruits.iterator();
while(it.hasNext()){
String fruit=(String)it.next();
if(fruit.startsWith("好吃的")){
m.addFruits(fruit);
}
}
return m;
}
public void restoreMemento(Memento memento){
this.menoy=memento.getMenoy();
this.fruits=memento.getFruits();
}
public String toString(){
return "Menoy:"+menoy+" ,Fruits:"+fruits;
}
}
SerializableMain类:
package zyr.dp.test;
import java.io.*;
import zyr.dp.serializable.Gamer;
import zyr.dp.serializable.Memento;
public class SerializableMain {
private static String filename="game.dat";
public static void main(String[] args) {
Gamer gamer=new Gamer(100);
Memento memento=loadMemento();
if(memento==null){
memento=gamer.createMemento();
}else{
System.out.println("从上次保存处开始...");
gamer.restoreMemento(memento);
}
for(int i=0;i<100;i++){
System.out.println("当前状态:"+i);
System.out.println("当前金额:"+gamer.getMenoy());
gamer.bet();
if(gamer.getMenoy()<memento.getMenoy()/2){
System.out.println("金钱过少,恢复到以前的状态:");
gamer.restoreMemento(memento);
System.out.println("此时状态为:"+gamer);
}else if(gamer.getMenoy()>memento.getMenoy()){
System.out.println("金钱增多,保存当前状态:");
memento=gamer.createMemento();
saveMemento(memento);
System.out.println("此时状态为:"+gamer);
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static void saveMemento(Memento memento) {
try {
ObjectOutput o=new ObjectOutputStream(new FileOutputStream(filename));
o.writeObject(memento);
o.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static Memento loadMemento() {
Memento memento=null;
ObjectInput in;
try {
in = new ObjectInputStream(new FileInputStream(filename));
memento=(Memento)in.readObject();
in.close();
} catch (FileNotFoundException e) {
System.out.println(e.toString());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return memento;
}
}
运行结果:
初次运行:
第二次运行:
可以看到保存和读取成功。
三、总结
备忘录模式也是一种非常常见的模式,用来保存对象的部分用于恢复的信息,和原型模式有着本质的区别,广泛运用在快照功能中,另外我们知道了宽接口和窄接口,这里的接口指的就是方法,没其他意思,以及类的可见性。同样的使用备忘录模式可以使得程序可以组件化,比如打算多次撤销当前的状态以及不仅可以撤销而且可以将当前的状态保存到文件中,我们不需要修改Gamer的代码就能做到,职责明确是一种非常重要的软件工程思想。
浅谈设计模式<最通俗易懂的讲解>