使用面向对象编程的方式实现撤销功能时,需要事先保存实例的相关状态信息。然后,在撤销时,还需要根据所保存的信息将实例恢复至原来的状态。
要想恢复实例,需要一个可以自由访问实例内部结构的权限。但是,如果稍不注意,又可能会将依赖于实例内部结构的代码分散地编写在程序中的各个地方,导致程序变得难以维护。这种情况就叫做“破坏了封装性”。
通过引入表示实例状态的角色,可以在保存和恢复实例时有效地防止对象的封装性遭到破坏。
使用Memento模式可以实现应用程序的以下功能。
- Undo(撤销)
- Redo(重做)
- History(历史纪录)
- Snapshot(快照)
示例程序
这是一个收集水果和获取金钱数的掷骰子游戏。游戏规则如下:
- 游戏是自动进行的。
- 游戏的主人公通过掷骰子来决定下一个状态。
- 当骰子点数为1的时候,主人公的金钱会增加。
- 当骰子点数为2的时候,主人公的金钱数会减少。
- 当骰子点数为6的时候,主人公会得到水果。
- 主人公没有钱的时候游戏就会结束。
Memento类
该类是表示Gamer主人公状态的类。Memento类和Gamer类位于一个包中。
package Memento;
import java.util.ArrayList;
import java.util.List;
//备忘录类
public class Memento {
//游戏者的钱数
int money;
//游戏者获得的水果数组
ArrayList<String> fruits;
//构造函数,实例化该类时只需要传入money变量
Memento(int money) {
// TODO Auto-generated constructor stub
this.money = money;
this.fruits = new ArrayList<>();
} //宽接口
//向外部只提供返回money的接口
public int getMoney(){
return money;
} //窄接口
//同包类通过该方法添加游戏者获得的水果,水果是在游戏者类中随机获得的
void addFruit(String fruit){
fruits.add(fruit);
} //宽接口
//获取水果数组
List<String> getFruits(){
return (List)fruits.clone();
} //窄接口
}
Gamer类
该类是表示游戏主人公的类。有三个字段,即所持金钱、获得的水果以及一个随机数生成器。而且还有一个名为fruitsname的静态字段。
package Memento;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
public class Gamer {
//钱数
private int money;
//水果数组
private List<String> fruits = new ArrayList<String>();
private Random random = new Random();
//静态水果数组名
private static String[] fruitsname = {"苹果","香蕉","葡萄","梨"};
public Gamer(int money) {
// TODO Auto-generated constructor stub
this.money = money;
}
//获取钱数
public int getMoney(){
return money;
}
//游戏规则
public void bet(){
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 f = getFruit();
System.out.println("获得了水果("+f+")");
fruits.add(f);
}else {
System.out.println("什么都没有发生");
}
}
public Memento createMemento(){
Memento memento = new Memento(money);
Iterator<String> iterator = fruits.iterator();
while (iterator.hasNext()) {
String f = (String)iterator.next();
if(f.startsWith("好吃的")){
memento.addFruit(f);
}
}
return memento;
}
public void restoreMemento(Memento memento){
this.money = memento.getMoney();
this.fruits = memento.getFruits();
}
public String toString(){
return "[money"+money+",fruits="+fruits+"]";
}
//返回水果字符串
private String getFruit() {
// TODO Auto-generated method stub
String prefix = "";
if(random.nextBoolean()){
prefix = "好吃的";
}
return prefix + fruitsname[random.nextInt(fruitsname.length)];
}
}
Main类
package Memento;
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建游戏者
Gamer gamer = new Gamer(100);
//创建备忘录对象,保存游戏初始状态
Memento memento = new Memento(gamer.getMoney());
//重复游戏100次
for(int i=0;i<100;i++){
System.out.println("====="+i);
System.out.println("当前状态"+gamer);
gamer.bet();
System.out.println("所持金钱为:"+gamer.getMoney()+"元");
//当前游戏金钱数增加了,则保存当前状态
if(gamer.getMoney()>memento.getMoney()){
System.out.println("所持金钱增加了许多,所以保存游戏当前状态");
gamer.createMemento();
}
//当前游戏金钱数减少了,则恢复至以前的状态
if(gamer.getMoney()<memento.getMoney()){
System.out.println("所持金钱减少了许多,所以恢复至游戏以前的状态");
gamer.restoreMemento(memento);
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO: handle exception
}
System.out.println("");
}
}
补充:
在Memento类中,只有getMoney方法是public的,它是一个窄接口,明明该方法带有修饰符public,但它却是一个窄接口,其实这里所说的“窄”是指外部可以操作的类内部的内容很少。像这种“能够获取的信息非常少”的状态就是这里的“窄”的意思。