• 浅谈设计模式-备忘录模式


    书接上回,本篇讲一下行为型模式-备忘录模式

    备忘录模式

    定义:在不破坏代码封装性的前提下,获取一个对象的内部状态并保存,后续可以将该对象恢复到原先保存的状态。

    UML图

    IMemento:备忘录。用来存储原发器(Originator)对象内部状态。可以使接口,也是是一个类。里面的方法,属性都是由原发器(Originator)对象内部状态决定,它是个被动接收方。并约定,该接口/类只允许原发器(Originator)对象访问。

    Originator:原发器。普通业务类,当需要存储当前时刻运行状态时,可以使用备忘录类记录下当前时刻状态,也可以在未来某个时刻恢复当前状态。

    Caretaker:备忘录管理者,当原发器(Originator)保存很多备忘录时,可以使用Caretaker类对众多备忘录对象进行管理。需要注意的是:管理者只有管理备忘录的功能,无法操作备忘录对象功能(比如查看备忘录内容等)

    IMemento

    1. /**
    2. * 备忘录接口
    3. * 也可以是一个类,这里以接口方式讲解
    4. */
    5. public interface IMemento {
    6. /**
    7. * 显示备忘录信息
    8. */
    9. void info();
    10. }

    Originator 

    1. /**
    2. * 原发器:
    3. * 创建备忘录的地方
    4. * 触发保存备忘对象的地方
    5. */
    6. public class Originator {
    7. private String state; //初始化状态
    8. //创建备忘录
    9. public IMemento createMemento(){
    10. return new MementoImpl(this.state);
    11. }
    12. //恢复Originator 原发器状态
    13. public void setMemento(IMemento memento){
    14. MementoImpl mi = (MementoImpl) memento;
    15. this.state = mi.state;
    16. }
    17. //备忘录实现对象,里面属性跟Originator 一样,用于缓存Originator对象状态
    18. private class MementoImpl implements IMemento{
    19. private String state;
    20. @Override
    21. public void info() {
    22. System.out.println("备忘录状态:" + state);
    23. }
    24. public MementoImpl(String state){
    25. this.state = state;
    26. }
    27. }
    28. public void setState(String state) {
    29. this.state = state;
    30. }
    31. public String getState() {
    32. return state;
    33. }
    34. }

    Caretaker 

    1. /**
    2. * 备忘录管理者
    3. */
    4. public class Caretaker {
    5. //维护所有备忘录对象
    6. private List mementos = new ArrayList<>();
    7. public void addMemento(IMemento memento){
    8. mementos.add(memento);
    9. }
    10. public IMemento retriveMemento(){
    11. if(mementos.size() > 0){
    12. return mementos.remove(mementos.size() - 1);
    13. }
    14. return null;
    15. }
    16. public void showMemento(){
    17. mementos.forEach(IMemento::info);
    18. }
    19. }

    测试

    1. public class App {
    2. public static void main(String[] args) {
    3. //原发器
    4. Originator originator = new Originator();
    5. //备份录管理器
    6. Caretaker caretaker = new Caretaker();
    7. //一系列操作后,原发器设置状态1
    8. originator.setState("state1");
    9. //备份1
    10. caretaker.addMemento(originator.createMemento());
    11. System.out.println("当前原发器状态:" + originator.getState() + ", 备份");
    12. //又一系列操作后,原发器设置状态2
    13. originator.setState("state2");
    14. caretaker.addMemento(originator.createMemento());
    15. System.out.println("当前原发器状态:" + originator.getState() + ", 备份");
    16. //又一系列操作后,原发器设置状态3
    17. originator.setState("state3");
    18. caretaker.addMemento(originator.createMemento());
    19. System.out.println("当前原发器状态:" + originator.getState() + ", 备份");
    20. //遍历备忘录集合
    21. caretaker.showMemento();
    22. //恢复备忘录....................
    23. originator.setMemento(caretaker.retriveMemento());
    24. System.out.println("第一次恢复备忘录,原发器状态:" + originator.getState());
    25. originator.setMemento(caretaker.retriveMemento());
    26. System.out.println("第二次恢复备忘录,原发器状态:" + originator.getState());
    27. originator.setMemento(caretaker.retriveMemento());
    28. System.out.println("第三次恢复备忘录,原发器状态:" + originator.getState());
    29. }
    30. }

    结果:

    1. 当前原发器状态:state1, 备份
    2. 当前原发器状态:state2, 备份
    3. 当前原发器状态:state3, 备份
    4. 备忘录状态:state1
    5. 备忘录状态:state2
    6. 备忘录状态:state3
    7. 第一次恢复备忘录,原发器状态:state3
    8. 第二次恢复备忘录,原发器状态:state2
    9. 第三次恢复备忘录,原发器状态:state1

    案例分析

    需求:模拟svn/git 版本commit命令与reset命令

    分析:svn/git 每次commit命令都会在服务器生成一个版本这就类似保存一个备忘录,将当前状态/文件缓存起来。而每次reset就是还原备忘录保存的状态/文件。

    UML图

    IVersonCtrlMemento

    备忘录接口,有获取commitid的方法与查看备忘录信息的方法。

    1. /**
    2. * 版本控制备忘录接口
    3. * 也可以是一个类,这里以接口方式讲解
    4. */
    5. public interface IVersonCtrlMemento {
    6. /**
    7. * 获取版本id
    8. * @return
    9. */
    10. String getCommentId();
    11. /**
    12. * 显示备忘录信息
    13. */
    14. void info();
    15. }

    GitOriginator 

    Git 的原发器,模拟Git的基本操作,比如commit提交(提交一个版本,也就是生成一个备忘录),reset回退(回退某个版本,表示回退备忘录),content属性理解为版本内容。

    1. /**
    2. * Git备忘录原发器
    3. */
    4. public class GitOriginator {
    5. //模拟保存版本内容
    6. private String content;
    7. //创建备忘录
    8. public IVersonCtrlMemento commit(){
    9. System.out.println("提交版本内容:" + this.content);
    10. return new GitMemento(this.content);
    11. }
    12. //恢复Originator 原发器状态
    13. public void reset(IVersonCtrlMemento memento){
    14. System.out.println("恢复版本,版本id:"+ memento.getCommentId());
    15. GitMemento mi = (GitMemento) memento;
    16. this.content = mi.content;
    17. System.out.println("恢复版本,版本内容:"+ this.content);
    18. }
    19. //Git备忘录
    20. private class GitMemento implements IVersonCtrlMemento {
    21. private String content;
    22. private Date date; //时间
    23. private String commitId; //使用uuid当做commit id
    24. @Override
    25. public String getCommentId() {
    26. return this.commitId;
    27. }
    28. @Override
    29. public void info() {
    30. System.out.println("当前版本时间:" + this.date);
    31. System.out.println("当前版本号:" + this.commitId);
    32. System.out.println("当前Git版本内容:" + content);
    33. System.out.println("---------------------------------");
    34. }
    35. public GitMemento(String content){
    36. this.content = content;
    37. this.date = new Date();
    38. this.commitId = UUID.randomUUID().toString();
    39. }
    40. }
    41. public String getContent() {
    42. return content;
    43. }
    44. public void setContent(String content) {
    45. this.content = content;
    46. }
    47. public void info(){
    48. System.out.println("当前版本内容:" + this.content);
    49. }
    50. }

    GitCaretaker

    备忘录管理者,类内部维护一个Map集合,key为commitId value为备忘录(git提交不同版本)

    1. /**
    2. * 备忘录管理者
    3. */
    4. public class GitCaretaker {
    5. private LinkedHashMap map = new LinkedHashMap<>();
    6. public void addMemento(IVersonCtrlMemento memento){
    7. if(memento != null){
    8. this.map.put(memento.getCommentId(), memento);
    9. }
    10. }
    11. public IVersonCtrlMemento retriveMemento(){
    12. //默认获取最后一个
    13. Iterator> iterator = map.entrySet().iterator();
    14. Map.Entry tail = null;
    15. while (iterator.hasNext()) {
    16. tail = iterator.next();
    17. }
    18. return tail.getValue();
    19. }
    20. public IVersonCtrlMemento retriveMemento(String commentId){
    21. return map.get(commentId);
    22. }
    23. public void showLog(){
    24. System.out.println("---------查看所有提交日志------------");
    25. map.values().forEach(IVersonCtrlMemento::info);
    26. }
    27. }

    App测试

    git commit 3次,然后回退2次,1:默认回退最新版,2:通过commitid回退

    1. public class App {
    2. public static void main(String[] args) {
    3. //备忘录-版本控制器
    4. GitCaretaker caretaker = new GitCaretaker();
    5. //git 原发器
    6. GitOriginator git = new GitOriginator();
    7. //模拟git版本操作-第一次
    8. git.setContent("version1 file operator");
    9. //提交1次--保存一个备忘录
    10. IVersonCtrlMemento versonCtrlMemento1 = git.commit();
    11. caretaker.addMemento(versonCtrlMemento1);
    12. //模拟git版本操作-第二次
    13. git.setContent("version2 file operator");
    14. IVersonCtrlMemento versonCtrlMemento2 = git.commit();
    15. caretaker.addMemento(versonCtrlMemento2);
    16. //模拟git版本操作-第三次
    17. git.setContent("version3 file operator");
    18. IVersonCtrlMemento versonCtrlMemento3 = git.commit();
    19. caretaker.addMemento(versonCtrlMemento3);
    20. //查看日志
    21. caretaker.showLog();
    22. //默认恢复
    23. git.reset(versonCtrlMemento3);
    24. //指定id恢复--第一次
    25. git.reset(caretaker.retriveMemento(versonCtrlMemento1.getCommentId()));
    26. }
    27. }

    结果

    1. 提交版本内容:version1 file operator
    2. 提交版本内容:version2 file operator
    3. 提交版本内容:version3 file operator
    4. ---------查看所有提交日志------------
    5. 当前版本时间:Sat Sep 10 15:14:06 CST 2022
    6. 当前版本号:37372d43-e413-42bc-b4be-d8a9b624d27e
    7. 当前Git版本内容:version1 file operator
    8. ---------------------------------
    9. 当前版本时间:Sat Sep 10 15:14:07 CST 2022
    10. 当前版本号:6574825d-2ebb-469e-ab26-4e47419dafbf
    11. 当前Git版本内容:version2 file operator
    12. ---------------------------------
    13. 当前版本时间:Sat Sep 10 15:14:07 CST 2022
    14. 当前版本号:1a88f92c-13ee-4a3b-a728-9214991fcd60
    15. 当前Git版本内容:version3 file operator
    16. ---------------------------------
    17. 恢复版本,版本id:1a88f92c-13ee-4a3b-a728-9214991fcd60
    18. 恢复版本,版本内容:version3 file operator
    19. 恢复版本,版本id:37372d43-e413-42bc-b4be-d8a9b624d27e
    20. 恢复版本,版本内容:version1 file operator
    21. Process finished with exit code 0

    解析

    上面结果就可以看到备忘录的实现过程,可能很多朋友有想法,为啥会存在备忘录管理者(GitCaretaker)这个概念,将备忘录逻辑放置到原发器(GitOriginator)中,它们功能二合一不行么?答案:语法上可以,设计上建议分开。管理者存在目的是管理备忘录,由客户端定制要恢复的哪个备忘录。简化原发器的逻辑。

    适用场景

    需要保存与恢复的数据相关业务场景,比如回退,撤销场景

    优缺点

    优点

    为用户提供可恢复的机制

    存档信息有更好的封装行

    缺点

    因为涉及类有3种角色,备忘录缓存需要空间,较为占用资源。

    开发案例

    这里找到一个例子,是Spring-webflow框架中有的小例子

    1. /**
    2. * A message context whose internal state can be managed by an external care-taker. State management employs the GOF
    3. * Memento pattern. This context can produce a serializable memento representing its internal state at any time. A
    4. * care-taker can then use that memento at a later time to restore any context instance to a previous state.
    5. *
    6. * @author Keith Donald
    7. */
    8. public interface StateManageableMessageContext extends MessageContext {
    9. /**
    10. * Create a serializable memento, or token representing a snapshot of the internal state of this message context.
    11. * @return the messages memento
    12. */
    13. public Serializable createMessagesMemento();
    14. /**
    15. * Set the state of this context from the memento provided. After this call, the messages in this context will match
    16. * what is encapsulated inside the memento. Any previous state will be overridden.
    17. * @param messagesMemento the messages memento
    18. */
    19. public void restoreMessages(Serializable messagesMemento);
    20. /**
    21. * Configure the message source used to resolve messages added to this context. May be set at any time to change how
    22. * coded messages are resolved.
    23. * @param messageSource the message source
    24. * @see MessageContext#addMessage(MessageResolver)
    25. */
    26. public void setMessageSource(MessageSource messageSource);
    27. }

    从备注与方法名称上看,很明显就能看出备忘录的痕迹,而该接口实现类:DefaultMessageContext 也是备忘录的Caretaker。

    总结

    备忘录模式的本质:保存与恢复内部状态。

  • 相关阅读:
    Vue--》超详细教程——vue-cli脚手架的搭建与使用
    物联网:用python调入机器学习分析物联网数据入侵检测模块
    大数据队列Kafka
    计算机网络常见的名词解释
    ML or DL
    共享按摩设备
    毫秒级!千万人脸库快速比对,上亿商品图片检索,背后的极速检索用了什么神器?
    JSTL标签库
    C语言之内存函数
    单目标应用:求解旅行商问题(TSP)的猎豹优化算法(The Cheetah Optimizer,CO)提供MATLAB代码
  • 原文地址:https://blog.csdn.net/langfeiyes/article/details/126722367