书接上回,本篇讲一下行为型模式-备忘录模式
备忘录模式
定义:在不破坏代码封装性的前提下,获取一个对象的内部状态并保存,后续可以将该对象恢复到原先保存的状态。
UML图
IMemento:备忘录。用来存储原发器(Originator)对象内部状态。可以使接口,也是是一个类。里面的方法,属性都是由原发器(Originator)对象内部状态决定,它是个被动接收方。并约定,该接口/类只允许原发器(Originator)对象访问。
Originator:原发器。普通业务类,当需要存储当前时刻运行状态时,可以使用备忘录类记录下当前时刻状态,也可以在未来某个时刻恢复当前状态。
Caretaker:备忘录管理者,当原发器(Originator)保存很多备忘录时,可以使用Caretaker类对众多备忘录对象进行管理。需要注意的是:管理者只有管理备忘录的功能,无法操作备忘录对象功能(比如查看备忘录内容等)
IMemento
/**
* 备忘录接口
* 也可以是一个类,这里以接口方式讲解
*/
public interface IMemento {
/**
* 显示备忘录信息
*/
void info();
}
Originator
/**
* 原发器:
* 创建备忘录的地方
* 触发保存备忘对象的地方
*/
public class Originator {
private String state; //初始化状态
//创建备忘录
public IMemento createMemento(){
return new MementoImpl(this.state);
}
//恢复Originator 原发器状态
public void setMemento(IMemento memento){
MementoImpl mi = (MementoImpl) memento;
this.state = mi.state;
}
//备忘录实现对象,里面属性跟Originator 一样,用于缓存Originator对象状态
private class MementoImpl implements IMemento{
private String state;
@Override
public void info() {
System.out.println("备忘录状态:" + state);
}
public MementoImpl(String state){
this.state = state;
}
}
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
Caretaker
/**
* 备忘录管理者
*/
public class Caretaker {
//维护所有备忘录对象
private List<IMemento> mementos = new ArrayList<>();
public void addMemento(IMemento memento){
mementos.add(memento);
}
public IMemento retriveMemento(){
if(mementos.size() > 0){
return mementos.remove(mementos.size() - 1);
}
return null;
}
public void showMemento(){
mementos.forEach(IMemento::info);
}
}
测试
public class App {
public static void main(String[] args) {
//原发器
Originator originator = new Originator();
//备份录管理器
Caretaker caretaker = new Caretaker();
//一系列操作后,原发器设置状态1
originator.setState("state1");
//备份1
caretaker.addMemento(originator.createMemento());
System.out.println("当前原发器状态:" + originator.getState() + ", 备份");
//又一系列操作后,原发器设置状态2
originator.setState("state2");
caretaker.addMemento(originator.createMemento());
System.out.println("当前原发器状态:" + originator.getState() + ", 备份");
//又一系列操作后,原发器设置状态3
originator.setState("state3");
caretaker.addMemento(originator.createMemento());
System.out.println("当前原发器状态:" + originator.getState() + ", 备份");
//遍历备忘录集合
caretaker.showMemento();
//恢复备忘录....................
originator.setMemento(caretaker.retriveMemento());
System.out.println("第一次恢复备忘录,原发器状态:" + originator.getState());
originator.setMemento(caretaker.retriveMemento());
System.out.println("第二次恢复备忘录,原发器状态:" + originator.getState());
originator.setMemento(caretaker.retriveMemento());
System.out.println("第三次恢复备忘录,原发器状态:" + originator.getState());
}
}
结果:
当前原发器状态:state1, 备份
当前原发器状态:state2, 备份
当前原发器状态:state3, 备份
备忘录状态:state1
备忘录状态:state2
备忘录状态:state3
第一次恢复备忘录,原发器状态:state3
第二次恢复备忘录,原发器状态:state2
第三次恢复备忘录,原发器状态:state1
案例分析
需求:模拟svn/git 版本commit命令与reset命令
分析:svn/git 每次commit命令都会在服务器生成一个版本这就类似保存一个备忘录,将当前状态/文件缓存起来。而每次reset就是还原备忘录保存的状态/文件。
UML图
IVersonCtrlMemento
备忘录接口,有获取commitid的方法与查看备忘录信息的方法。
/**
* 版本控制备忘录接口
* 也可以是一个类,这里以接口方式讲解
*/
public interface IVersonCtrlMemento {
/**
* 获取版本id
* @return
*/
String getCommentId();
/**
* 显示备忘录信息
*/
void info();
}
GitOriginator
Git 的原发器,模拟Git的基本操作,比如commit提交(提交一个版本,也就是生成一个备忘录),reset回退(回退某个版本,表示回退备忘录),content属性理解为版本内容。
/**
* Git备忘录原发器
*/
public class GitOriginator {
//模拟保存版本内容
private String content;
//创建备忘录
public IVersonCtrlMemento commit(){
System.out.println("提交版本内容:" + this.content);
return new GitMemento(this.content);
}
//恢复Originator 原发器状态
public void reset(IVersonCtrlMemento memento){
System.out.println("恢复版本,版本id:"+ memento.getCommentId());
GitMemento mi = (GitMemento) memento;
this.content = mi.content;
System.out.println("恢复版本,版本内容:"+ this.content);
}
//Git备忘录
private class GitMemento implements IVersonCtrlMemento {
private String content;
private Date date; //时间
private String commitId; //使用uuid当做commit id
@Override
public String getCommentId() {
return this.commitId;
}
@Override
public void info() {
System.out.println("当前版本时间:" + this.date);
System.out.println("当前版本号:" + this.commitId);
System.out.println("当前Git版本内容:" + content);
System.out.println("---------------------------------");
}
public GitMemento(String content){
this.content = content;
this.date = new Date();
this.commitId = UUID.randomUUID().toString();
}
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public void info(){
System.out.println("当前版本内容:" + this.content);
}
}
GitCaretaker
备忘录管理者,类内部维护一个Map集合,key为commitId value为备忘录(git提交不同版本)
/**
* 备忘录管理者
*/
public class GitCaretaker {
private LinkedHashMap<String, IVersonCtrlMemento> map = new LinkedHashMap<>();
public void addMemento(IVersonCtrlMemento memento){
if(memento != null){
this.map.put(memento.getCommentId(), memento);
}
}
public IVersonCtrlMemento retriveMemento(){
//默认获取最后一个
Iterator<Map.Entry<String, IVersonCtrlMemento>> iterator = map.entrySet().iterator();
Map.Entry<String, IVersonCtrlMemento> tail = null;
while (iterator.hasNext()) {
tail = iterator.next();
}
return tail.getValue();
}
public IVersonCtrlMemento retriveMemento(String commentId){
return map.get(commentId);
}
public void showLog(){
System.out.println("---------查看所有提交日志------------");
map.values().forEach(IVersonCtrlMemento::info);
}
}
App测试
git commit 3次,然后回退2次,1:默认回退最新版,2:通过commitid回退
public class App {
public static void main(String[] args) {
//备忘录-版本控制器
GitCaretaker caretaker = new GitCaretaker();
//git 原发器
GitOriginator git = new GitOriginator();
//模拟git版本操作-第一次
git.setContent("version1 file operator");
//提交1次--保存一个备忘录
IVersonCtrlMemento versonCtrlMemento1 = git.commit();
caretaker.addMemento(versonCtrlMemento1);
//模拟git版本操作-第二次
git.setContent("version2 file operator");
IVersonCtrlMemento versonCtrlMemento2 = git.commit();
caretaker.addMemento(versonCtrlMemento2);
//模拟git版本操作-第三次
git.setContent("version3 file operator");
IVersonCtrlMemento versonCtrlMemento3 = git.commit();
caretaker.addMemento(versonCtrlMemento3);
//查看日志
caretaker.showLog();
//默认恢复
git.reset(versonCtrlMemento3);
//指定id恢复--第一次
git.reset(caretaker.retriveMemento(versonCtrlMemento1.getCommentId()));
}
}
结果
提交版本内容:version1 file operator
提交版本内容:version2 file operator
提交版本内容:version3 file operator
---------查看所有提交日志------------
当前版本时间:Sat Sep 10 15:14:06 CST 2022
当前版本号:37372d43-e413-42bc-b4be-d8a9b624d27e
当前Git版本内容:version1 file operator
---------------------------------
当前版本时间:Sat Sep 10 15:14:07 CST 2022
当前版本号:6574825d-2ebb-469e-ab26-4e47419dafbf
当前Git版本内容:version2 file operator
---------------------------------
当前版本时间:Sat Sep 10 15:14:07 CST 2022
当前版本号:1a88f92c-13ee-4a3b-a728-9214991fcd60
当前Git版本内容:version3 file operator
---------------------------------
恢复版本,版本id:1a88f92c-13ee-4a3b-a728-9214991fcd60
恢复版本,版本内容:version3 file operator
恢复版本,版本id:37372d43-e413-42bc-b4be-d8a9b624d27e
恢复版本,版本内容:version1 file operator
Process finished with exit code 0
解析
上面结果就可以看到备忘录的实现过程,可能很多朋友有想法,为啥会存在备忘录管理者(GitCaretaker)这个概念,将备忘录逻辑放置到原发器(GitOriginator)中,它们功能二合一不行么?答案:语法上可以,设计上建议分开。管理者存在目的是管理备忘录,由客户端定制要恢复的哪个备忘录。简化原发器的逻辑。
适用场景
需要保存与恢复的数据相关业务场景,比如回退,撤销场景
优缺点
优点
为用户提供可恢复的机制
存档信息有更好的封装行
缺点
因为涉及类有3种角色,备忘录缓存需要空间,较为占用资源。
开发案例
这里找到一个例子,是Spring-webflow框架中有的小例子
/**
* A message context whose internal state can be managed by an external care-taker. State management employs the GOF
* Memento pattern. This context can produce a serializable memento representing its internal state at any time. A
* care-taker can then use that memento at a later time to restore any context instance to a previous state.
*
* @author Keith Donald
*/
public interface StateManageableMessageContext extends MessageContext {
/**
* Create a serializable memento, or token representing a snapshot of the internal state of this message context.
* @return the messages memento
*/
public Serializable createMessagesMemento();
/**
* Set the state of this context from the memento provided. After this call, the messages in this context will match
* what is encapsulated inside the memento. Any previous state will be overridden.
* @param messagesMemento the messages memento
*/
public void restoreMessages(Serializable messagesMemento);
/**
* Configure the message source used to resolve messages added to this context. May be set at any time to change how
* coded messages are resolved.
* @param messageSource the message source
* @see MessageContext#addMessage(MessageResolver)
*/
public void setMessageSource(MessageSource messageSource);
}
从备注与方法名称上看,很明显就能看出备忘录的痕迹,而该接口实现类:DefaultMessageContext 也是备忘录的Caretaker。
总结
备忘录模式的本质:保存与恢复内部状态。