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

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

备忘录模式

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

UML图

浅谈设计模式-备忘录模式_第1张图片

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图

浅谈设计模式-备忘录模式_第2张图片

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。

总结

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

你可能感兴趣的:(浅谈设计模式,设计模式,备忘录模式)