@(设计模式)[设计模式, 备忘录模式, memento]
备忘录模式提供的基本功能是:保存对象状态信息(快照)、撤销、重做和历史记录。
备忘录模式一般会提供两种接口:宽接口和窄接口。通过宽接口可以获取整个对象状态,会暴露备忘录对象的内部信息。通过窄接口,只能访问有限的,开发者限制了的信息,可以有效的防止信息泄露。
package com.pc.memento.example;
import java.util.ArrayList;
import java.util.List;
/**
* 备忘录类
* Created by Switch on 2017/3/31.
*/
public class Memento {
/**
* 所持金钱
*/
private int money;
/**
* 获得的水果
*/
private List fruits;
/**
* 获取当前所持金钱
*
* @return 所持金钱
*/
public int getMoney() {
return money;
}
/**
* 构造方法,初始化所持钱数
*
* @param money 初始化钱数
*/
Memento(int money) {
this.money = money;
this.fruits = new ArrayList<>();
}
/**
* 添加水果
*
* @param fruit 水果
*/
void addFruit(String fruit) {
fruits.add(fruit);
}
/**
* 添加当前所持的所有水果
*
* @return 水果列表
*/
List getFruits() {
return fruits;
}
}
package com.pc.memento.example;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
/**
* 游戏者类
* Created by Switch on 2017/3/31.
*/
public class Gamer {
/**
* 所持金钱
*/
private int money;
/**
* 获得的水果
*/
private List fruits = new ArrayList<>();
/**
* 随机数生成器
*/
private Random random = new Random();
/**
* 表示水果种类的数组
*/
private static String[] fruitsname = {"苹果", "葡萄", "香蕉", "橘子"};
/**
* 构造方法,初始化金钱数
*
* @param money 金钱数
*/
public Gamer(int money) {
this.money = money;
}
/**
* 获取当前所持金钱数
*
* @return 金钱数
*/
public int getMoney() {
return money;
}
/**
* 投掷骰子进行游戏
*/
public void bet() {
// 获取骰子数值
int dice = random.nextInt(6) + 1;
if (dice == 1) {
this.money += 100;
System.out.println("所持金钱增加了。");
} else if (dice == 2) {
this.money /= 2;
System.out.println("所持金钱减半了。");
} else if (dice == 6) {
String f = this.getFruit();
System.out.println("获得了水果(" + f + ")。");
this.fruits.add(f);
} else {
System.out.println("什么都没有发生。");
}
}
/**
* 创建备忘录
*
* @return 备忘录对象
*/
public Memento createMemento() {
Memento m = new Memento(money);
Iterator it = fruits.iterator();
while (it.hasNext()) {
String fruit = it.next();
if (fruit.startsWith("好吃的")) {
m.addFruit(fruit);
}
}
return m;
}
/**
* 撤销到指定备忘
*
* @param memento 备忘录对象
*/
public void restoreMemento(Memento memento) {
this.money = memento.getMoney();
this.fruits = memento.getFruits();
}
/**
* 获得一个水果
*
* @return 水果
*/
private String getFruit() {
String prefix = "";
if (random.nextBoolean()) {
prefix = "好吃的";
}
return prefix + fruitsname[random.nextInt(fruitsname.length)];
}
@Override
public String toString() {
return "Gamer{ money=" + money + ", fruits=" + fruits + '}';
}
}
package com.pc.memento.example.test;
import com.pc.memento.example.Gamer;
import com.pc.memento.example.Memento;
import org.junit.Test;
/**
* Memento Tester.
*
* @author Switch
* @version 1.0
*/
public class MementoTest {
/**
* 测试备忘录模式
*/
@Test
public void testMemento() {
Gamer gamer = new Gamer(100);
Memento memento = gamer.createMemento();
for (int i = 0; i < 20; i++) {
System.out.println("==== " + i);
System.out.println("当前状态:" + gamer);
// 投掷骰子
gamer.bet();
System.out.println("所持金钱为" + gamer.getMoney() + "元。");
// 如何处理memento
if (gamer.getMoney() > memento.getMoney()) {
System.out.println(" (所持金钱增加了许多,因此保存游戏当前的状态)");
memento = gamer.createMemento();
} else if (gamer.getMoney() < memento.getMoney() / 2) {
System.out.println(" (所持金钱减少了许多,因此将游戏恢复至以前的状态)");
gamer.restoreMemento(memento);
}
// 等待一段时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
System.out.println("");
}
}
}
==== 0
当前状态:Gamer{ money=100, fruits=[]}
什么都没有发生。
所持金钱为100元。
==== 1
当前状态:Gamer{ money=100, fruits=[]}
所持金钱减半了。
所持金钱为50元。
==== 2
当前状态:Gamer{ money=50, fruits=[]}
所持金钱减半了。
所持金钱为25元。
(所持金钱减少了许多,因此将游戏恢复至以前的状态)
==== 3
当前状态:Gamer{ money=100, fruits=[]}
获得了水果(好吃的葡萄)。
所持金钱为100元。
==== 4
当前状态:Gamer{ money=100, fruits=[好吃的葡萄]}
所持金钱增加了。
所持金钱为200元。
(所持金钱增加了许多,因此保存游戏当前的状态)
==== 5
当前状态:Gamer{ money=200, fruits=[好吃的葡萄]}
所持金钱减半了。
所持金钱为100元。
==== 6
当前状态:Gamer{ money=100, fruits=[好吃的葡萄]}
获得了水果(葡萄)。
所持金钱为100元。
==== 7
当前状态:Gamer{ money=100, fruits=[好吃的葡萄, 葡萄]}
所持金钱增加了。
所持金钱为200元。
==== 8
当前状态:Gamer{ money=200, fruits=[好吃的葡萄, 葡萄]}
所持金钱减半了。
所持金钱为100元。
==== 9
当前状态:Gamer{ money=100, fruits=[好吃的葡萄, 葡萄]}
所持金钱增加了。
所持金钱为200元。
==== 10
当前状态:Gamer{ money=200, fruits=[好吃的葡萄, 葡萄]}
什么都没有发生。
所持金钱为200元。
==== 11
当前状态:Gamer{ money=200, fruits=[好吃的葡萄, 葡萄]}
所持金钱增加了。
所持金钱为300元。
(所持金钱增加了许多,因此保存游戏当前的状态)
==== 12
当前状态:Gamer{ money=300, fruits=[好吃的葡萄, 葡萄]}
所持金钱增加了。
所持金钱为400元。
(所持金钱增加了许多,因此保存游戏当前的状态)
==== 13
当前状态:Gamer{ money=400, fruits=[好吃的葡萄, 葡萄]}
所持金钱减半了。
所持金钱为200元。
==== 14
当前状态:Gamer{ money=200, fruits=[好吃的葡萄, 葡萄]}
获得了水果(葡萄)。
所持金钱为200元。
==== 15
当前状态:Gamer{ money=200, fruits=[好吃的葡萄, 葡萄, 葡萄]}
获得了水果(好吃的橘子)。
所持金钱为200元。
==== 16
当前状态:Gamer{ money=200, fruits=[好吃的葡萄, 葡萄, 葡萄, 好吃的橘子]}
什么都没有发生。
所持金钱为200元。
==== 17
当前状态:Gamer{ money=200, fruits=[好吃的葡萄, 葡萄, 葡萄, 好吃的橘子]}
所持金钱增加了。
所持金钱为300元。
==== 18
当前状态:Gamer{ money=300, fruits=[好吃的葡萄, 葡萄, 葡萄, 好吃的橘子]}
所持金钱减半了。
所持金钱为150元。
(所持金钱减少了许多,因此将游戏恢复至以前的状态)
==== 19
当前状态:Gamer{ money=400, fruits=[好吃的葡萄]}
什么都没有发生。
所持金钱为400元。
Originator
角色会在保存自己的最新状态时生成Memento
角色。当把以前保存的Memento
角色传递给Originator
角色时,它会将自己恢复至生成该Memento
角色时的状态。在案例中,由Gamer
类扮演此角色。
Memento
角色会将Originator
角色的内部信息整合在一起。在Memento
角色中虽然保存了Originator
角色的信息,但它不会向外部公开这些信息。
Memento
角色有以下两种接口(API
)。
wide interface
——宽接口(API
) Memento
角色提供的“宽接口(API
)”是指所有用于获取恢复对象状态信息的方法的集合。由于宽接口(API
)会暴露所有Memento
角色的内部信息,因此能够使用宽接口(API
)的只有Originator
角色。narrow interface
——窄接口(API
) Memento
角色为外部的Caretaker
角色提供了“窄接口(API
)” 。可以通过窄接口(API
)获取的Memento
角色的内部信息、非常有限,因此可以有效地防止信息泄露。通过对外提供以上两种接口(API
),可以有效地防止对象的封装性被破坏。
在案例中,由Memento
类扮演此角色。
Originator
角色和Memento
角色之间有着非常紧密的联系。
当Caretaker
角色想要保存当前的Originator
角色的状态时,会通知Originator
角色。Originator
角色在接收到通知后会生成Memento
角色的实例并将其返回给Caretaker
角色。由于以后可能会用Memento
实例来将Originator
恢复至原来的状态,因此Caretaker
角色会一直保存Memento
实例。在案例中,由测试类扮演此角色。
不过,Caretaker
角色只能使用Memento
角色两种接口(API
)中的窄接口(API
),也就是说它无法访问Memento
角色内部的所有信息。它只是将Originator
角色生成的Memento
角色当作一个黑盒子保存起来。
虽然Originator
角色和Memento
角色之间是强关联关系,但Caretaker
角色和Memento
角色之间是弱关联关系。Memento
角色对Caretaker
角色隐藏了自身的内部信息。
GitHub:DesignPatternStudy
——————参考《图解设计模式》