提升网站建设品质公司,秦皇岛做网站汉狮网络,成都网站建设团队,wordpress asp.net重学 Java 设计模式#xff1a;实战责任链模式「模拟618电商大促期间#xff0c;项目上线流程多级负责人审批场景」
一、前言
场地和场景的重要性
射击#x1f3f9;需要去靶场学习、滑雪#x1f3c2;需要去雪场体验、开车#x1f697;需要能上路实践#xff0c;而编程…重学 Java 设计模式实战责任链模式「模拟618电商大促期间项目上线流程多级负责人审批场景」
一、前言
场地和场景的重要性
射击需要去靶场学习、滑雪需要去雪场体验、开车需要能上路实践而编程开发除了能完成产品的功能流程还需要保证系统的可靠性能。就像你能听到的一些系统监控指标QPS、TPS、TP99、TP999、可用率、响应时长等等而这些指标的总和评估就是一个系统的健康度。但如果你几乎没有听到这样的技术术语也没接触过类似高并发场景那么就很像驾驶证的科目1考了100分但不能上路。没有这样的技术场景给你训练让你不断的体会系统的脾气秉性即便你有再多的想法都没法实现。所以如果真的想学习一定要去一个有实操的场景下水试试才能学会狗刨。
你的视觉盲区有多大
同样一本书、同样一条路、同样一座城你真的以为生活有选择吗有时候很多选项都是摆设给你多少次机会你都选的一模一样。这不是你选不选而是你的认知范围决定了你下一秒做的事情另外的一个下一秒又决定了再下一个下一秒。就像管中窥豹一样20%的面积在你视觉里都是黑色的甚至就总是忽略看不到而这看不到的20%就是生命中的时运但人可以学习可以成长可以脱胎换骨可以努力付出通过一次次的蜕变而看到剩下的20%
没有设计图纸你敢盖楼吗
编程开发中最好的什么是设计。运用架构思维、经验心得、才华灵感构建出最佳的系统。真正的研发会把自己写的代码当做作品来欣赏你说这是一份工作但在这样的人眼里这可不是一份工作而是一份工匠精神。就像可能时而你也会为自己因为一个niubility的设计而豪迈万丈为能上线一个扛得住每秒200万访问量的系统会精神焕发。这样的自豪感就是一次次垒砖一样垫高脚底不断的把你的视野提高让你能看到上层设计也能知晓根基建设。可以把控全局也可以治理细节。这一份份知识的沉淀来帮助你绘制出一张系统架构蓝图。
二、开发环境
JDK 1.8Idea Maven涉及工程三个可以通过关注公众号bugstack虫洞栈回复源码下载获取(打开获取的链接找到序号18)
工程描述itstack-demo-design-13-00场景模拟工程模拟一个上线流程审批的接口。itstack-demo-design-13-01使用一坨代码实现业务需求itstack-demo-design-13-02通过设计模式优化改造代码产生对比性从而学习
三、责任链模式介绍 图片来自https://refactoringguru.cn/design-patterns/chain-of-responsibility
击鼓传雷看上图你是否想起周星驰有一个电影大家坐在海边围成一个圈拿着一个点燃的炸弹互相传递。
责任链模式的核心是解决一组服务中的先后执行处理关系就有点像你没钱花了需要家庭财务支出审批10块钱以下找闺女审批100块钱先闺女审批在媳妇审批。你可以理解想象成当你要跳槽的时候被安排的明明白白的被各个领导签字放行。
四、案例场景模拟 在本案例中我们模拟在618大促期间的业务系统上线审批流程场景
像是这些一线电商类的互联网公司阿里、京东、拼多多等在618期间都会做一些运营活动场景以及提供的扩容备战就像过年期间百度的红包一样。但是所有开发的这些系统都需要陆续的上线因为临近618有时候也有一些紧急的调整的需要上线但为了保障线上系统的稳定性是尽可能的减少上线的也会相应的增强审批力度。就像一级响应、二级响应一样。
而这审批的过程在随着特定时间点会增加不同级别的负责人加入每个人就像责任链模式中的每一个核心点。对于研发小伙伴并不需要关心具体的审批流程处理细节只需要知道这个上线更严格级别也更高但对于研发人员来说同样是点击相同的提审按钮等待审核。
接下来我们就模拟这样一个业务诉求场景使用责任链的设计模式来实现此功能。
1. 场景模拟工程
itstack-demo-design-13-00
└── src└── main└── java└── org.itstack.demo.design└── AuthService.java这里的代码结构比较简单只有一个模拟审核和查询审核结果的服务类。相当于你可以调用这个类去审核工程和获取审核结构这部分结果信息是模拟的写到缓存实现。
2. 场景简述
2.1 模拟审核服务
public class AuthService {private static MapString, Date authMap new ConcurrentHashMapString, Date();public static Date queryAuthInfo(String uId, String orderId) {return authMap.get(uId.concat(orderId));}public static void auth(String uId, String orderId) {authMap.put(uId.concat(orderId), new Date());}}这里面提供了两个接口一个是查询审核结果(queryAuthInfo)、另外一个是处理审核(auth)。这部分是把由谁审核的和审核的单子ID作为唯一key值记录到内存Map结构中。
五、用一坨坨代码实现
这里我们先使用最直接的方式来实现功能
按照我们的需求审批流程平常系统上线只需要三级负责人审批就可以但是到了618大促时间点就需要由二级负责以及一级负责人一起加入审批系统上线流程。在这里我们使用非常直接的if判断方式来实现这样的需求。
1. 工程结构
itstack-demo-design-13-01
└── src└── main└── java└── org.itstack.demo.design└── AuthController.java这部分非常简单的只包含了一个审核的控制类就像有些伙伴开始写代码一样一个类写所有需求。
2. 代码实现
public class AuthController {private SimpleDateFormat f new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);// 时间格式化public AuthInfo doAuth(String uId, String orderId, Date authDate) throws ParseException {// 三级审批Date date AuthService.queryAuthInfo(1000013, orderId);if (null date) return new AuthInfo(0001, 单号, orderId, 状态待三级审批负责人 , 王工);// 二级审批if (authDate.after(f.parse(2020-06-01 00:00:00)) authDate.before(f.parse(2020-06-25 23:59:59))) {date AuthService.queryAuthInfo(1000012, orderId);if (null date) return new AuthInfo(0001, 单号, orderId, 状态待二级审批负责人 , 张经理);}// 一级审批if (authDate.after(f.parse(2020-06-11 00:00:00)) authDate.before(f.parse(2020-06-20 23:59:59))) {date AuthService.queryAuthInfo(1000011, orderId);if (null date) return new AuthInfo(0001, 单号, orderId, 状态待一级审批负责人 , 段总);}return new AuthInfo(0001, 单号, orderId, 状态审批完成);}}这里从上到下分别判断了在指定时间范围内由不同的人员进行审批就像618上线的时候需要三个负责人都审批才能让系统进行上线。像是这样的功能看起来很简单的但是实际的业务中会有很多部门但如果这样实现就很难进行扩展并且在改动扩展调整也非常麻烦。
3. 测试验证
3.1 编写测试类
Test
public void test_AuthController() throws ParseException {AuthController authController new AuthController(); // 模拟三级负责人审批logger.info(测试结果{}, JSON.toJSONString(authController.doAuth(小傅哥, 1000998004813441, new Date())));logger.info(测试结果{}, 模拟三级负责人审批王工);AuthService.auth(1000013, 1000998004813441); // 模拟二级负责人审批 logger.info(测试结果{}, JSON.toJSONString(authController.doAuth(小傅哥, 1000998004813441, new Date())));logger.info(测试结果{}, 模拟二级负责人审批张经理);AuthService.auth(1000012, 1000998004813441); // 模拟一级负责人审批logger.info(测试结果{}, JSON.toJSONString(authController.doAuth(小傅哥, 1000998004813441, new Date())));logger.info(测试结果{}, 模拟一级负责人审批段总);AuthService.auth(1000011, 1000998004813441); logger.info(测试结果{}, 审批完成);
}这里模拟每次查询是否审批完成随着审批的不同节点之后继续由不同的负责人进行审批操作。authController.doAuth是查看审批的流程节点、AuthService.auth是审批方法用于操作节点流程状态。
3.2 测试结果
23:25:00.363 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果{code:0001,info:单号1000998004813441 状态待三级审批负责人 王工}
23:25:00.366 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果模拟三级负责人审批王工
23:25:00.367 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果{code:0001,info:单号1000998004813441 状态待二级审批负责人 张经理}
23:25:00.367 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果模拟二级负责人审批张经理
23:25:00.368 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果{code:0001,info:单号1000998004813441 状态待一级审批负责人 段总}
23:25:00.368 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果模拟一级负责人审批段总
23:25:00.368 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果审批完成Process finished with exit code 0从测试结果上可以看到一层层的由不同的人员进行审批审批完成后到下一个人进行处理。单看结果是满足我们的诉求只不过很难扩展和调整流程相当于代码写的死死的。
六、责任链模式重构代码
接下来使用装饰器模式来进行代码优化也算是一次很小的重构。
责任链模式可以让各个服务模块更加清晰而每一个模块间可以通过next的方式进行获取。而每一个next是由继承的统一抽象类实现的。最终所有类的职责可以动态的进行编排使用编排的过程可以做成可配置化。
1. 工程结构
itstack-demo-design-13-02
└── src└── main└── java└── org.itstack.demo.design├── impl│ ├── Level1AuthLink.java│ ├── Level2AuthLink.java│ └── Level3AuthLink.java├── AuthInfo.java└── AuthLink.java责任链类图 责任链模式模型结构 上图是这个业务模型中责任链结构的核心部分通过三个实现了统一抽象类AuthLink的不同规则再进行责任编排模拟出一条链路。这个链路就是业务中的责任链。一般在使用责任链时候如果是场景比较固定可以通过写死到代码中进行初始化。但如果业务场景经常变化可以做成xml配置的方式进行处理也可以落到库里进行初始化操作。
2. 代码实现
2.1 责任链中返回对象定义
public class AuthInfo {private String code;private String info ;public AuthInfo(String code, String ...infos) {this.code code;for (String str:infos){this.info this.info.concat(str);}}// ...get/set
}这个类的是包装了责任链处理过程中返回结果的类方面处理每个责任链的返回信息。
2.2 链路抽象类定义
public abstract class AuthLink {protected Logger logger LoggerFactory.getLogger(AuthLink.class);protected SimpleDateFormat f new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);// 时间格式化protected String levelUserId; // 级别人员IDprotected String levelUserName; // 级别人员姓名private AuthLink next; // 责任链public AuthLink(String levelUserId, String levelUserName) {this.levelUserId levelUserId;this.levelUserName levelUserName;}public AuthLink next() {return next;}public AuthLink appendNext(AuthLink next) {this.next next;return this;}public abstract AuthInfo doAuth(String uId, String orderId, Date authDate);}这部分是责任链链接起来的核心部分。AuthLink next重点在于可以通过next方式获取下一个链路需要处理的节点。levelUserId、levelUserName是责任链中的公用信息标记每一个审核节点的人员信息。抽象类中定义了一个抽象方法abstract AuthInfo doAuth这是每一个实现者必须实现的类不同的审核级别处理不同的业务。
2.3 三个审核实现类
Level1AuthLink
public class Level1AuthLink extends AuthLink {public Level1AuthLink(String levelUserId, String levelUserName) {super(levelUserId, levelUserName);}public AuthInfo doAuth(String uId, String orderId, Date authDate) {Date date AuthService.queryAuthInfo(levelUserId, orderId);if (null date) {return new AuthInfo(0001, 单号, orderId, 状态待一级审批负责人 , levelUserName);}AuthLink next super.next();if (null next) {return new AuthInfo(0000, 单号, orderId, 状态一级审批完成负责人, 时间, f.format(date), 审批人, levelUserName);}return next.doAuth(uId, orderId, authDate);}}Level2AuthLink
public class Level2AuthLink extends AuthLink {private Date beginDate f.parse(2020-06-11 00:00:00);private Date endDate f.parse(2020-06-20 23:59:59);public Level2AuthLink(String levelUserId, String levelUserName) throws ParseException {super(levelUserId, levelUserName);}public AuthInfo doAuth(String uId, String orderId, Date authDate) {Date date AuthService.queryAuthInfo(levelUserId, orderId);if (null date) {return new AuthInfo(0001, 单号, orderId, 状态待二级审批负责人 , levelUserName);}AuthLink next super.next();if (null next) {return new AuthInfo(0000, 单号, orderId, 状态二级审批完成负责人, 时间, f.format(date), 审批人, levelUserName);}if (authDate.before(beginDate) || authDate.after(endDate)) {return new AuthInfo(0000, 单号, orderId, 状态二级审批完成负责人, 时间, f.format(date), 审批人, levelUserName);}return next.doAuth(uId, orderId, authDate);}}Level3AuthLink
public class Level3AuthLink extends AuthLink {private Date beginDate f.parse(2020-06-01 00:00:00);private Date endDate f.parse(2020-06-25 23:59:59);public Level3AuthLink(String levelUserId, String levelUserName) throws ParseException {super(levelUserId, levelUserName);}public AuthInfo doAuth(String uId, String orderId, Date authDate) {Date date AuthService.queryAuthInfo(levelUserId, orderId);if (null date) {return new AuthInfo(0001, 单号, orderId, 状态待三级审批负责人 , levelUserName);}AuthLink next super.next();if (null next) {return new AuthInfo(0000, 单号, orderId, 状态三级审批负责人完成, 时间, f.format(date), 审批人, levelUserName);}if (authDate.before(beginDate) || authDate.after(endDate)) {return new AuthInfo(0000, 单号, orderId, 状态三级审批负责人完成, 时间, f.format(date), 审批人, levelUserName);}return next.doAuth(uId, orderId, authDate);}}如上三个类Level1AuthLink、Level2AuthLink、Level3AuthLink实现了不同的审核级别处理的简单逻辑。例如第一个审核类中会先判断是否审核通过如果没有审核通过则返回结果给调用方引导去审核。这里简单模拟审核后有时间信息不为空作为判断条件判断完成后获取下一个审核节点super.next();如果不存在下一个节点则直接返回结果。之后是根据不同的业务时间段进行判断是否需要二级和一级的审核。最后返回下一个审核结果next.doAuth(uId, orderId, authDate);有点像递归调用。
3. 测试验证
3.1 编写测试类
Test
public void test_AuthLink() throws ParseException {AuthLink authLink new Level3AuthLink(1000013, 王工).appendNext(new Level2AuthLink(1000012, 张经理).appendNext(new Level1AuthLink(1000011, 段总)));logger.info(测试结果{}, JSON.toJSONString(authLink.doAuth(小傅哥, 1000998004813441, new Date())));// 模拟三级负责人审批AuthService.auth(1000013, 1000998004813441);logger.info(测试结果{}, 模拟三级负责人审批王工);logger.info(测试结果{}, JSON.toJSONString(authLink.doAuth(小傅哥, 1000998004813441, new Date())));// 模拟二级负责人审批AuthService.auth(1000012, 1000998004813441);logger.info(测试结果{}, 模拟二级负责人审批张经理);logger.info(测试结果{}, JSON.toJSONString(authLink.doAuth(小傅哥, 1000998004813441, new Date())));// 模拟一级负责人审批AuthService.auth(1000011, 1000998004813441);logger.info(测试结果{}, 模拟一级负责人审批段总);logger.info(测试结果{}, JSON.toJSONString(authLink.doAuth(小傅哥, 1000998004813441, new Date())));
}这里包括最核心的责任链创建实际的业务中会包装到控制层 AuthLink authLink new Level3AuthLink(1000013, 王工) .appendNext(new Level2AuthLink(1000012, 张经理) .appendNext(new Level1AuthLink(1000011, 段总))); 通过把不同的责任节点进行组装构成一条完整业务的责任链。接下里不断的执行查看审核链路authLink.doAuth(...)通过返回结果对数据进行3、2、1级负责人审核直至最后审核全部完成。
3.2 测试结果
23:49:46.585 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果{code:0001,info:单号1000998004813441 状态待三级审批负责人 王工}
23:49:46.590 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果模拟三级负责人审批王工
23:49:46.590 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果{code:0001,info:单号1000998004813441 状态待二级审批负责人 张经理}
23:49:46.590 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果模拟二级负责人审批张经理
23:49:46.590 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果{code:0001,info:单号1000998004813441 状态待一级审批负责人 段总}
23:49:46.590 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果模拟一级负责人审批段总
23:49:46.590 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果{code:0000,info:单号1000998004813441 状态一级审批完成负责人 时间2020-06-18 23:49:46 审批人段总}Process finished with exit code 0从上述的结果可以看到我们的责任链已经生效按照责任链的结构一层层审批直至最后输出审批结束到一级完成的结果。这样责任链的设计方式可以方便的进行扩展和维护也把if语句干掉了。
七、总结
从上面代码从if语句重构到使用责任链模式开发可以看到我们的代码结构变得清晰干净了也解决了大量if语句的使用。并不是if语句不好只不过if语句并不适合做系统流程设计但是在做判断和行为逻辑处理中还是非常可以使用的。在我们前面学习结构性模式中讲到过组合模式它像是一颗组合树一样我们搭建出一个流程决策树。其实这样的模式也是可以和责任链模型进行组合扩展使用而这部分的重点在于如何关联链路的关联最终的执行都是在执行在中间的关系链。责任链模式很好的处理单一职责和开闭原则简单了耦合也使对象关系更加清晰而且外部的调用方并不需要关心责任链是如何进行处理的*(以上程序中可以把责任链的组合进行包装在提供给外部使用)*。但除了这些优点外也需要是适当的场景才进行使用避免造成性能以及编排混乱调试测试疏漏问题。