重学 Java 设计模式:实战备忘录模式「模拟互联网系统上线过程当中,配置文件回滚场景」


做者:小傅哥
博客:https://bugstack.cn - 原创系列专题文章html

沉淀、分享、成长,让本身和他人都能有所收获!😄

1、前言

实现不了是研发的借口?java

实现不了,有时候是功能复杂度较高难以实现,有时候是工期较短实现不完。而编码的行为又是一个不太好量化的过程,一样一个功能每一个人的实现方式不同,遇到开发问题解决问题的速度也不同。除此以外还很很差给产品解释具体为何要这个工期时间,这就像盖楼的图纸最终要多少水泥砂浆同样。那么这时研发会尽量的去经过一些经验,制定流程规范、设计、开发、评审等,肯定一个能够完成的时间范围,又避免风险的时间点后。再被压缩,每每会出一些矛盾点,能压缩要解释为何以前要那么多时间,不能压缩又有各方不断施加的压力。所以有时候不必定是借口,是要考虑如何让整个团队健康的发展。程序员

鼓励有时比压力要重要!数据库

在学习的过程当中,不少时候咱们听到的都是,你要怎样,怎样,你瞧瞧谁谁谁,哪怕今天听不到这样的声音了,但由于曾经反复听到过而致使心里抗拒。虽然也知道本身要去学,可是很难坚持,学着学着就没有了方向,看到还有那么多不会的就更慌了,以致于最后心态崩了,更不肯意学。其实程序员的压力并不小,想成长几乎是须要一直的学习,就像彷佛不再敢说精通java了同样,知识量实在是随着学习的深刻,愈来愈深,愈来愈广。因此须要,开心学习,快乐成长!设计模式

临阵的你好像一直很着急!安全

常常的听到;老师明天就要了你帮我弄弄吧你给我写一下完事我就学此次着急如今这不是没时间学吗快给我看看。其实看到的相似的还有不少,很纳闷你的着急怎么来的,不太可能,人在家中坐,祸从天上落。老师怎么就那个时间找你了,老板怎么就今天管你要了,还不是日积月累你没有学习,临时抱佛脚乱着急!即便后来真的有人帮你了,但最好不要放松,要尽快学会,躲得过初一还有初二呢!微信

2、开发环境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程一个,能够经过关注公众号bugstack虫洞栈,回复源码下载获取(打开获取的连接,找到序号18)
工程 描述
itstack-demo-design-17-00 开发配置文件备忘录

3、备忘录模式介绍

备忘录模式,图片来自 refactoringguru.cn

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

这个设计在咱们日常的生活或者开发中也是比较常见的,好比:后悔药、孟婆汤(一下回滚到0),IDEA编辑和撤销、小霸王游戏机存档。固然还有咱们很是常见的Photoshop,以下;app

Photoshop 历史记录

4、案例场景模拟

场景模拟;系统发布上线配置回滚

在本案例中咱们模拟系统在发布上线的过程当中记录线上配置文件用于紧急回滚单元测试

在大型互联网公司系统的发布上线必定是易用、安全、可处理紧急情况的,同时为了能够隔离线上和本地环境,通常会把配置文件抽取出来放到线上,避免有人误操做致使本地的配置内容发布出去。同时线上的配置文件也会在每次变动的时候进行记录,包括;版本号、时间、MD五、内容信息和操做人。

在后续上线时若是发现紧急问题,系统就会须要回滚操做,若是执行回滚那么也能够设置配置文件是否回滚。由于每个版本的系统可能会随着带着一些配置文件的信息,这个时候就能够很方便的让系统与配置文件一块儿回滚操做。

咱们接下来就使用备忘录模式,模拟如何记录配置文件信息。实际的使用过程当中还会将信息存放到库中进行保存,这里暂时只是使用内存记录。

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

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

除了如今的这个案例外,还能够是运营人员在后台erp建立活动对信息的记录,方便运营人员能够上下修改本身的版本,而不至于由于误操做而丢失信息。

1. 工程结构

itstack-demo-design-17-00
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── Admin.java
    │           ├── ConfigFile.java
    │           ├── ConfigMemento.java
    │           └── ConfigOriginator.java
    └── test
        └── java
            └── org.itstack.demo.design.test
                └── ApiTest.java

备忘录模式模型结构

备忘录模式模型结构

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

2. 代码实现

2.1 配置信息类

public class ConfigFile {

    private String versionNo; // 版本号
    private String content;   // 内容
    private Date dateTime;    // 时间
    private String operator;  // 操做人
    
    // ...get/set
}
  • 配置类能够是任何形式的,这里只是简单的描述了一个基本的配置内容信息。

2.2 备忘录类

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;
    }
    
}
  • 备忘录是对原有配置类的扩展,能够设置和获取配置信息。

2.3 记录者类

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,这部分须要注意。

2.4 管理员类

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),这样四个操做方法。

3. 测试验证

3.1 编写测试类

@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()));
}
  • 这个设计模式的学习有一部分重点是体如今了单元测试类上,这里包括了四次的信息存储和备忘录历史配置操做。
  • 经过上面添加了四次配置后,下面分别进行操做是;回滚1次再回滚1次以后向前进1次最后是获取指定的版本配置。具体的效果能够参考测试结果。

3.2 测试结果

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

Process finished with exit code 0
  • 从测试效果上能够看到,历史配置按照咱们的指令进行了回滚和前进,以及最终经过指定的版本进行获取,符合预期结果。

6、总结

  • 此种设计模式的方式能够知足在不破坏原有属性类的基础上,扩充了备忘录的功能。虽然和咱们平时使用的思路是同样的,但在具体实现上还能够细细品味,这样的方式在一些源码中也有所体现。
  • 在以上的实现中咱们是将配置模拟存放到内存中,若是关机了会致使配置信息丢失,由于在一些真实的场景里仍是须要存放到数据库中。那么此种存放到内存中进行回复的场景也不是没有,好比;Photoshop、运营人员操做ERP配置活动,那么也就是即时性的通常不须要存放到库中进行恢复。另外若是是使用内存方式存放备忘录,须要考虑存储问题,避免形成内存大量消耗。
  • 设计模式的学习都是为了更好的写出可扩展、可管理、易维护的代码,而这个学习的过程须要本身不断的尝试实际操做,理论的知识与实际结合还有很长一段距离。切记多多上手!

7、推荐阅读

相关文章
相关标签/搜索