侧边栏壁纸
博主头像
qingtian博主等级

喜欢是一件细水流长的事,是永不疲惫的双向奔赴~!

  • 累计撰写 94 篇文章
  • 累计创建 42 个标签
  • 累计收到 1 条评论

设计模式之备忘录模式

qingtian
2020-12-26 / 0 评论 / 0 点赞 / 988 阅读 / 6,711 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2020-12-31,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

设计模式之备忘录模式

来源自《重学Java设计模式》链接提取码:ytc3

备忘录模式

image-20201225151542157

备忘录模式是以可以恢复或者说回滚,配置、版本、悔棋为核心功能的设计模式,而这种设计模式属于行为模式。在功能实现上是以不破坏原对象为基础增加备忘录操作类,记录原对象的行为从而实现备忘录模式。

这个设计在我们平常的生活或者开发中也是比较常见的。

案例场景模拟

image-20201226130729580

在本案例中我们模拟系统在发布上线的过程中记录线上配置文件用于紧急回滚。

在大型互联网公司系统的发布上线一定是易用、安全、可处理紧急状况的,同时为了可以隔离线上和本地环境,一般会把配置文件抽取出来放到线上,避免有人误操作导致本地的配置内容发布出去。同时线上的配置文件也会在每次变更的时候进行记录,包括:版本号、时间、MD5、内容信息和操作人。

在后续上线时如果发现紧急问题,系统就会需要回滚操作,如果执行回滚那么也可以设置配置文件是否回滚。因为每个版本的系统可能会随着带一些配置文件的信息,这个时候就可以很方便的让系统与配置文件一起回滚操作。

备忘录模式记录配置文件版本信息

备忘录的设计模式实现方式,重点在于不更改原有类的基础上,增加备忘录类存放记录。可能平时虽然不一定非得按照这个设计模式的代码结构来实现自己的需求,但是对于功能上可能也完成过类似的功能,记录系统的信息。

除了现在的这个案例外,还可以是运营人员在后台erp创建活动对信息的记录,方便运营人员可以上下修改自己的版本,而不至于因为误操作而丢失信息。

备忘录模式结构模型

image-20201226134448046

  • 以上是工程结构的一个类图,其实相对来说并不复杂,除了原有的配置类ConfigFile以外,只增加了三个类。
  • ConfigMemento:备忘录类,相当于是对原有配置类的扩展。
  • ConfigOriginator:记录者类,获取和返回备忘录类对象信息。
  • admin:管理员类,用于操作记录备忘信息,比如你一些列的顺序执行了什么或者某个版本下的内容信息。

代码实现

配置信息类

public class ConfigFile {

    private String versionNo; // 版本号
    private String content;   // 内容
    private Date dateTime;    // 时间
    private String operator;  // 操作人

    public ConfigFile(String versionNo, String content, Date dateTime, String operator) {
        this.versionNo = versionNo;
        this.content = content;
        this.dateTime = dateTime;
        this.operator = operator;
    }

    public String getVersionNo() {
        return versionNo;
    }

    public void setVersionNo(String versionNo) {
        this.versionNo = versionNo;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Date getDateTime() {
        return dateTime;
    }

    public void setDateTime(Date dateTime) {
        this.dateTime = dateTime;
    }

    public String getOperator() {
        return operator;
    }

    public void setOperator(String operator) {
        this.operator = operator;
    }
}

备忘录类

public class ConfigMemento {

    private ConfigFile configFile;

    public ConfigMemento(ConfigFile configFile) {
        this.configFile = configFile;
    }

    public ConfigFile getConfigFile() {
        return configFile;
    }

    public void setConfigFile(ConfigFile configFile) {
        this.configFile = configFile;
    }
    
}
  • 备忘录是对原有配置类的扩展,可以设置和获取配置信息

记录者类

public class ConfigOriginator {

    private ConfigFile configFile;

    public ConfigFile getConfigFile() {
        return configFile;
    }

    public void setConfigFile(ConfigFile configFile) {
        this.configFile = configFile;
    }

    public ConfigMemento saveMemento(){
        return new ConfigMemento(configFile);
    }

    public void getMemento(ConfigMemento memento){
        this.configFile = memento.getConfigFile();
    }

}
  • 记录者类除了对ConfigFile配置类增加了获取和设置方法外,还增加了保存saveMemento()、获取getMemento(ConfigMemento memento)
  • saveMemento:保存备忘录的时候会创建一个备忘录信息,并返回回去,交给管理者处理。
  • getMemento:获取的之后并不是直接返回,而是把备忘录的信息交给现在的配置文件this.configFile,这部分需要注意。

管理员类

public class Admin {

    private int cursorIdx = 0;
    private List<ConfigMemento> mementoList = new ArrayList<ConfigMemento>();
    private Map<String, ConfigMemento> mementoMap = new ConcurrentHashMap<String, ConfigMemento>();

    public void append(ConfigMemento memento) {
        mementoList.add(memento);
        mementoMap.put(memento.getConfigFile().getVersionNo(), memento);
        cursorIdx++;
    }

    public ConfigMemento undo() {
        if (--cursorIdx <= 0) return mementoList.get(0);
        return mementoList.get(cursorIdx);
    }

    public ConfigMemento redo() {
        if (++cursorIdx > mementoList.size()) return mementoList.get(mementoList.size() - 1);
        return mementoList.get(cursorIdx);
    }

    public ConfigMemento get(String versionNo){
        return mementoMap.get(versionNo);
    }

}
  • 在这个类中主要实现的核心功能就是记录配置文件信息,也就是备忘录的效果,之后提供可以回滚和获取的方法,拿到备忘录的具体内容。
  • 同时这里设置了两个数据结构来存放备忘录,实际使用中可以按需设置。List<ConfigMemento>Map<String,ConfigMemento>
  • 最后是提供的备忘录操作方法:存放(append)、回滚(undo)、返回(redo)、定向获取(get),这样四种操作方法。

测试验证

public class ApiTest {

    private Logger logger = LoggerFactory.getLogger(ApiTest.class);

    @Test
    public void test() {

        Admin admin = new Admin();

        ConfigOriginator configOriginator = new ConfigOriginator();

        configOriginator.setConfigFile(new ConfigFile("1000001", "配置内容A=哈哈", new Date(), "小傅哥"));
        admin.append(configOriginator.saveMemento()); // 保存配置

        configOriginator.setConfigFile(new ConfigFile("1000002", "配置内容A=嘻嘻", new Date(), "小傅哥"));
        admin.append(configOriginator.saveMemento()); // 保存配置

        configOriginator.setConfigFile(new ConfigFile("1000003", "配置内容A=么么", new Date(), "小傅哥"));
        admin.append(configOriginator.saveMemento()); // 保存配置

        configOriginator.setConfigFile(new ConfigFile("1000004", "配置内容A=嘿嘿", new Date(), "小傅哥"));
        admin.append(configOriginator.saveMemento()); // 保存配置

        // 历史配置(回滚)
        configOriginator.getMemento(admin.undo());
        logger.info("历史配置(回滚)undo:{}", JSON.toJSONString(configOriginator.getConfigFile()));

        // 历史配置(回滚)
        configOriginator.getMemento(admin.undo());
        logger.info("历史配置(回滚)undo:{}", JSON.toJSONString(configOriginator.getConfigFile()));

        // 历史配置(前进)
        configOriginator.getMemento(admin.redo());
        logger.info("历史配置(前进)redo:{}", JSON.toJSONString(configOriginator.getConfigFile()));

        // 历史配置(获取)
        configOriginator.getMemento(admin.get("1000002"));
        logger.info("历史配置(获取)get:{}", JSON.toJSONString(configOriginator.getConfigFile()));

    }

}

result

14:46:10.266 [main] INFO  org.itstack.demo.design.test.ApiTest - 历史配置(回滚)undo:{"content":"配置内容A=嘿嘿","dateTime":1608965169856,"operator":"小傅哥","versionNo":"1000004"}
14:46:10.280 [main] INFO  org.itstack.demo.design.test.ApiTest - 历史配置(回滚)undo:{"content":"配置内容A=么么","dateTime":1608965169856,"operator":"小傅哥","versionNo":"1000003"}
14:46:10.280 [main] INFO  org.itstack.demo.design.test.ApiTest - 历史配置(前进)redo:{"content":"配置内容A=嘿嘿","dateTime":1608965169856,"operator":"小傅哥","versionNo":"1000004"}
14:46:10.280 [main] INFO  org.itstack.demo.design.test.ApiTest - 历史配置(获取)get:{"content":"配置内容A=嘻嘻","dateTime":1608965169856,"operator":"小傅哥","versionNo":"1000002"}

Process finished with exit code 0

总结

  • 此种设计模式的方式可以满足在不破坏原有属性类的基础上,扩充了备忘录的功能。虽然和我们平时使用的思路是一样的,但在具体实现上还可以细细品味,这样的方式在一些源码中也有所体现。
  • 在以上的实现中我们是将配置模拟存放在内存章,如果关机了会导致配置信息丢失,因为在一些真实场景中还是需要放到数据库中。那么此种存放在内存中进行恢复的场景也不是没有,比如:Photoshop、运营人员操作ERP配置活动,那么就是即时性的一般不需要存放在数据库中进行恢复。
0

评论区