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

作者:小傅哥
博客:https://bugstack.cn - 原创系列专题文章
沉淀、分享、成长,让自己和他人都能有所收获!
一、前言
实现不了是研发的借口?
实现不了,有时候是功能复杂度较高难以实现,有时候是工期较短实现不完。而编码的行为又是一个不太好量化的过程,同样一个功能每个人的实现方式不一样,遇到开发问题解决问题的速度也不一样。除此之外还很不好给产品解释具体为什么要这个工期时间,这就像盖楼的图纸最终要多少水泥砂浆一样。那么这时研发会尽可能的去通过一些经验,制定流程规范、设计、开发、评审等,确定一个可以完成的时间范围,又避免风险的时间点后。再被压缩,往往会出一些矛盾点,能压缩要解释为什么之前要那么多时间,不能压缩又有各方不断施加的压力。因此有时候不一定是借口,是要考虑如何让整个团队健康的发展。
鼓励有时比压力要重要!
在学习的过程中,很多时候我们听到的都是,你要怎样,怎样,你瞧瞧谁谁谁,哪怕今天听不到这样的声音了,但因为曾经反复听到过而导致内心抗拒。虽然也知道自己要去学,但是很难坚持,学着学着就没有了方向,看到还有那么多不会的就更慌了,以至于最后心态崩了,更不愿意学。其实程序员的压力并不小,想成长几乎是需要一直的学习,就像似乎再也不敢说精通java了一样,知识量实在是随着学习的深入,越来越深,越来越广。所以需要,开心学习,快乐成长!
临阵的你好像一直很着急!
经常的听到;老师明天就要了你帮我弄弄吧、你给我写一下完事我就学这次着急、现在这不是没时间学吗快给我看看。其实看到的类似的还有很多,很纳闷你的着急怎么来的,不太可能,人在家中坐,祸从天上落。老师怎么就那个时间找你了,老板怎么就今天管你要了,还不是日积月累你没有学习,临时抱佛脚乱着急!即使后来真的有人帮你了,但最好不要放松,要尽快学会,躲得过初一还有初二呢!
二、开发环境
- JDK 1.8
- Idea + Maven
- 涉及工程一个,可以通过关注公众号:
bugstack虫洞栈,回复源码下载获取(打开获取的链接,找到序号18)
| 工程 | 描述 |
|---|---|
| itstack-demo-design-17-00 | 开发配置文件备忘录 |
三、备忘录模式介绍
备忘录模式是以可以恢复或者说回滚,配置、版本、悔棋为核心功能的设计模式,而这种设计模式属于行为模式。在功能实现上是以不破坏原对象为基础增加备忘录操作类,记录原对象的行为从而实现备忘录模式。
这个设计在我们平常的生活或者开发中也是比较常见的,比如:后悔药、孟婆汤(一下回滚到0),IDEA编辑和撤销、小霸王游戏机存档。当然还有我们非常常见的Photoshop,如下;
四、案例场景模拟
在本案例中我们模拟系统在发布上线的过程中记录线上配置文件用于紧急回滚
在大型互联网公司系统的发布上线一定是易用、安全、可处理紧急状况的,同时为了可以隔离线上和本地环境,一般会把配置文件抽取出来放到线上,避免有人误操作导致本地的配置内容发布出去。同时线上的配置文件也会在每次变更的时候进行记录,包括;版本号、时间、MD5、内容信息和操作人。
在后续上线时如果发现紧急问题,系统就会需要回滚操作,如果执行回滚那么也可以设置配置文件是否回滚。因为每一个版本的系统可能会随着带着一些配置文件的信息,这个时候就可以很方便的让系统与配置文件一起回滚操作。
我们接下来就使用备忘录模式,模拟如何记录配置文件信息。实际的使用过程中还会将信息存放到库中进行保存,这里暂时只是使用内存记录。
五、备忘录模式记录配置文件版本信息
备忘录的设计模式实现方式,重点在于不更改原有类的基础上,增加备忘录类存放记录。可能平时虽然不一定非得按照这个设计模式的代码结构来实现自己的需求,但是对于功能上可能也完成过类似的功能,记录系统的信息。
除了现在的这个案例外,还可以是运营人员在后台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
- 从测试效果上可以看到,历史配置按照我们的指令进行了回滚和前进,以及最终通过指定的版本进行获取,符合预期结果。
六、总结
- 此种设计模式的方式可以满足在不破坏原有属性类的基础上,扩充了备忘录的功能。虽然和我们平时使用的思路是一样的,但在具体实现上还可以细细品味,这样的方式在一些源码中也有所体现。
- 在以上的实现中我们是将配置模拟存放到内存中,如果关机了会导致配置信息丢失,因为在一些真实的场景里还是需要存放到数据库中。那么此种存放到内存中进行回复的场景也不是没有,比如;Photoshop、运营人员操作ERP配置活动,那么也就是即时性的一般不需要存放到库中进行恢复。另外如果是使用内存方式存放备忘录,需要考虑存储问题,避免造成内存大量消耗。
- 设计模式的学习都是为了更好的写出可扩展、可管理、易维护的代码,而这个学习的过程需要自己不断的尝试实际操作,理论的知识与实际结合还有很长一段距离。切记多多上手!
七、推荐阅读
1. 重学 Java 设计模式:实战工厂方法模式「多种类型商品不同接口,统一发奖服务搭建场景」2. 重学 Java 设计模式:实战原型模式「上机考试多套试,每人题目和答案乱序排列场景」3. 重学 Java 设计模式:实战桥接模式「多支付渠道(微信、支付宝)与多支付模式(刷脸、指纹)场景」4. 重学 Java 设计模式:实战组合模式「营销差异化人群发券,决策树引擎搭建场景」5. 重学 Java 设计模式:实战外观模式「基于SpringBoot开发门面模式中间件,统一控制接口白名单场景」6. 重学 Java 设计模式:实战享元模式「基于Redis秒杀,提供活动与库存信息查询场景」7. 重学 Java 设计模式:实战备忘录模式「模拟互联网系统上线过程中,配置文件回滚场景」
重学 Java 设计模式:实战备忘录模式「模拟互联网系统上线过程中,配置文件回滚场景」的更多相关文章
- 重学 Java 设计模式:实战状态模式「模拟系统营销活动,状态流程审核发布上线场景」
作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! @ 目录 一.前言 二.开发环境 三.状态模式介绍 四.案例场景模拟 1 ...
- 重学 Java 设计模式:实战访问者模式「模拟家长与校长,对学生和老师的不同视角信息的访问场景」
作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 能力,是你前行的最大保障 年龄会不断的增长,但是什么才能让你不 ...
- 重学 Java 设计模式:实战责任链模式「模拟618电商大促期间,项目上线流程多级负责人审批场景」
作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 场地和场景的重要性 射击
- 重学 Java 设计模式:实战迭代器模式「模拟公司组织架构树结构关系,深度迭代遍历人员信息输出场景」
作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 相信相信的力量! 从懵懂的少年,到拿起键盘,可以写一个Hell ...
- 重学 Java 设计模式:实战外观模式「基于SpringBoot开发门面模式中间件,统一控制接口白名单场景」
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你感受到的容易,一定有人为你承担不容易 这句话更像是描述生活的,许许多多的磕磕绊绊总 ...
- 重学 Java 设计模式:实战享元模式「基于Redis秒杀,提供活动与库存信息查询场景」
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 程序员的上下文是什么? 很多时候一大部分编程开发的人员都只是关注于功能的实现,只 ...
- 重学 Java 设计模式:实战代理模式「模拟mybatis-spring中定义DAO接口,使用代理类方式操作数据库原理实现场景」
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 难以跨越的瓶颈期,把你拿捏滴死死的! 编程开发学习过程中遇到的瓶颈期,往往是由于看不 ...
- 重学 Java 设计模式:实战桥接模式(多支付渠道「微信、支付宝」与多支付模式「刷脸、指纹」场景)
作者:小傅哥 博客:https://bugstack.cn - 编写系列原创专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 为什么你的代码那么多ifelse 同类的业务.同样的功能, ...
- 重学 Java 设计模式:实战装饰器模式(SSO单点登录功能扩展,增加拦截用户访问方法范围场景)
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 对于代码你有编程感觉吗 很多人写代码往往是没有编程感觉的,也就是除了可以把功能按照固 ...
随机推荐
- Java实现 LeetCode 792 自定义字符串排序(暴力)
792. 匹配子序列的单词数 给定字符串 S 和单词字典 words, 求 words[i] 中是 S 的子序列的单词个数. 示例: 输入: S = "abcde" words = ...
- Java实现 LeetCode 712 两个字符串的最小ASCII删除和(最长公共子串&&ASCII值最小)
712. 两个字符串的最小ASCII删除和 给定两个字符串s1, s2,找到使两个字符串相等所需删除字符的ASCII值的最小和. 示例 1: 输入: s1 = "sea", s2 ...
- Java实现 LeetCode 6 Z字形变换
6. Z 字形变换 将一个给定字符串根据给定的行数,以从上往下.从左到右进行 Z 字形排列. 比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下: L ...
- ntpq无法查询同步信息,显示The specified class was not found
年初时工班发现工作站和服务器都没办法用ntpq看时钟同步了,如下图所示.输入ntpq-p 就显示"The specified class was not found" 通过排查,发 ...
- (三)SQLMap工具-使用选项的操作命令&功能
目录结构 1.Options(选项) 2.Target(目标) 3.Request(请求) 4.Optimization(优化) 5.Injection(注入) 6.Detection(检测) 7.T ...
- 温故知新-java的I/O模型-BIO&NIO&AIO
文章目录 摘要 传统的BIO编程 伪异步I/O编程 NIO编程 AIO编程 几种IO模型的对比 netty 参考 你的鼓励也是我创作的动力 Posted by 微博@Yangsc_o 原创文章,版权声 ...
- 分布式锁没那么难,手把手教你实现 Redis 分布锁!|保姆级教程
书接上文 上篇文章「MySQL 可重复读,差点就让我背上了一个 P0 事故!」发布之后,收到很多小伙伴们的留言,从中又学习到很多,总结一下. 上篇文章可能举得例子有点不恰当,导致有些小伙伴没看懂为什么 ...
- 并发编程之sun.misc.Unsafe类
1.Unsafe知识点整理 2.代码: package com.javabasic.unsafe; import java.lang.reflect.Field; import sun.misc.Un ...
- Quartz SpringBoot 简单整合一下
一次简单的代码整合记录. 数据库准备 如果是MySQL可能出现一些小问题.比如联合主键长度超限制,已经记录解决办法了. CREATE TABLE QRTZ_JOB_DETAILS ( SCHED_NA ...
- excel如何快速计算日期对应的生肖?
是否可以根据日期统计出生肖? 牛闪闪想应该可以吧!结果搜到了一个巨牛无比的公式. =MID("猴鸡狗猪鼠牛虎兔龙蛇马羊",MOD(YEAR(B2),12)+1,1), 利用年份 ...