书接上回,本篇讲一下行为型模式-备忘录模式
定义:在不破坏代码封装性的前提下,获取一个对象的内部状态并保存,后续可以将该对象恢复到原先保存的状态。
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
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
map = new LinkedHashMap<>(); -
- public void addMemento(IVersonCtrlMemento memento){
- if(memento != null){
- this.map.put(memento.getCommentId(), memento);
- }
- }
-
- public IVersonCtrlMemento retriveMemento(){
- //默认获取最后一个
- Iterator
> iterator = map.entrySet().iterator(); - Map.Entry
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。
备忘录模式的本质:保存与恢复内部状态。