复杂业务场景下,如何优雅的使用设计模式来优化代码?

1、引言

本文以一个实际案例来介绍在解决业务需求的路上,如何通过常用的设计模式来逐级优化我们的代码,以把我们所了解的到设计模式真实的应用于实战。

2、背景

假定我们现在有一个订单流程管理系统,这个系统对于用户发起的一笔订单,需要你编写代码按照以下环节进行依次处理

图片

注:本文不会对每个环节的实现细节进行描述,读者也不必了解这每个环节的实现,我们只需要关注代码架构设计

3、第一次迭代

按照背景,我们如果不是打算if-else一撸到底的话,我们最合适使用的设计模式应该是责任链模式,于是我们先打算用责任链模式来做我们的第一次迭代。

图片

先整体看下类图:

图片

我们定义一个抽象类,抽象类中定义了下一个处理器,方便后期我们读取配置直接构建责任链处理顺序:

@Data
public abstract class BizOrderHandler {/*** 下一个处理器*/private BizOrderHandler nextBizOrderHandler;/*** 处理器执行方法* @param param 责任链参数* @return 执行结果*/public abstract Result handle(ProductVO param);/*** 链路传递* @param param* @return*/protected Result next(ProductVO param) {//下一个链路没有处理器了,直接返回if (Objects.isNull(nextBizOrderHandler)) {return Result.success();}//执行下一个处理器return nextBizOrderHandler.handle(param);}}

然后我们将需要实现的流程都来实现这个接口 (为了简单只列举一个)

public class StorageCheckBizOrderHandler extends BizOrderHandler {@Overridepublic Result handle(ProductVO param) {// 这里写上仓储管理的业务逻辑System.out.println("StorageCheckBizOrderHandler doing business!");return super.next(param);}
}

通过调用父类的next方法实现了链式传递,接下来我们就可以使用责任链来实现业务了

public class OrderHandleCases {static Map<String, BizOrderHandler> handlerMap = new HashMap<>();static {handlerMap.put("Storage", new StorageCheckBizOrderHandler());handlerMap.put("Payment", new PaymentBizOrderHandler());handlerMap.put("RightCenter", new RightCenterBizOrderHandler());handlerMap.put("PointDeduction", new PointDeductBizOrderHandler());}public static void main(String[] args) { // 这里可以从nacos配置中心读取责任链配置BizOrderHandler handler1 = initHandler(Lists.newArrayList("Storage", "RightCenter", "PointDeduction","Payment"));// 虚拟化一个产品订单ProductVO productVO = ProductVO.builder().build();Result result = handler1.handle(productVO);System.out.println("订单处理 成功");}/*** 根据责任链配置构建责任链* @param handlerNameChain 责任链执行顺序* @return 首个处理器*/private static BizOrderHandler initHandler(List<String> handlerNameChain) {List<BizOrderHandler> handlers = new ArrayList<>();for (int i = 0; i < handlerNameChain.size(); i++) {String cur = handlerNameChain.get(i);String next = i + 1 < handlerNameChain.size() ? handlerNameChain.get((i + 1)) : null;BizOrderHandler handler = handlerMap.get(cur);if (next != null) {handler.setNextBizOrderHandler(handlerMap.get(next));}handlers.add(handler);}return handlers.get(0);}}

上面的代码中通过initHandler这个方法来组建整个责任链条,还能实现动态配置,比如后期需要撤掉积分模块的商品处理,改个配置就行,感觉责任链完美搞定了这个问题,第一版就这样开心上线。

图片

4、第二次迭代

产品又来了,提了一个新的需求

  • 产品说,我们需要支持多租户,每种租户的订单流程都是不一样的

  • 租户A:仓储检查->权益扣减->积分扣减->剩余金额支付

  • 租户B:仓储检查->积分扣减->权益扣减

也就是说现在流程变成这样:

图片

来了多租户,这有何难,再加一条责任链配置不就好了,直接写代码如下:

public class OrderHandleCases {static Map<String, BizOrderHandler> handlerMap = new HashMap<>();static {handlerMap.put("Storage", new StorageCheckBizOrderHandler());handlerMap.put("Payment", new PaymentBizOrderHandler());handlerMap.put("RightCenter", new RightCenterBizOrderHandler());handlerMap.put("PointDeduction", new PointDeductBizOrderHandler());}public static void main(String[] args) {// 租户A的责任链BizOrderHandler handler1 = initHandler(Lists.newArrayList("Storage", "RightCenter", "PointDeduction", "Payment"));ProductVO productVO = ProductVO.builder().build();Result result1 = handler1.handle(productVO);// 租户B的责任链BizOrderHandler handler2 = initHandler(Lists.newArrayList("Storage", "PointDeduction", "RightCenter"));Result result2 = handler2.handle(productVO);System.out.println("订单处理 成功");}/*** 根据责任链配置构建责任链* @param handlerNameChain 责任链执行顺序* @return 首个处理器*/private static BizOrderHandler initHandler(List<String> handlerNameChain) {List<BizOrderHandler> handlers = new ArrayList<>();for (int i = 0; i < handlerNameChain.size(); i++) {String cur = handlerNameChain.get(i);String next = i + 1 < handlerNameChain.size() ? handlerNameChain.get((i + 1)) : null;BizOrderHandler handler = handlerMap.get(cur);if (next != null) {handler.setNextBizOrderHandler(handlerMap.get(next));}handlers.add(handler);}return handlers.get(0);}}

上面的代码相比之前的就是多加了一条新的租户B的责任链配置。感觉这个功能不就实现了嘛 结果一运行:堆栈溢出!

图片

咋回事 怎么堆栈溢出了,咱们仔细看一下 发现咱们的Map里面存放的实例全部是单例,搞出来了环形链表了....

图片

上图中黑色箭头代表第一个租户的流程,绿色箭头代表第二个租户,第二个租户的流程在执行到权益扣减环节,后面由于第一个租户配置的下一个环节是积分扣减,于是在这里形成了环。

看来单例不行,咱们得搞多例 既然需要多次构建对象,于是咱们搬出来下一个设计模式“简单工厂模式”:

public class BizOrderHandlerFactory {public static BizOrderHandler buildBizOrderHandler(String bizType) {switch (bizType) {case "Storage":return new StorageCheckBizOrderHandler();case "Payment":return new PaymentBizOrderHandler();case "RightCenter":return new RightCenterBizOrderHandler();case "PointDeduction":return new PointDeductBizOrderHandler();default:return null;}}}

然后我们改写initHandler方法,不再从map中取实例,转为从工厂方法里面获取实例:

private static BizOrderHandler initHandlerPro(List<String> handlerNameChain) {List<BizOrderHandler> handlers = new ArrayList<>();for (String s : handlerNameChain) {BizOrderHandler handler = BizOrderHandlerFactory.buildBizOrderHandler(s);handlers.add(handler);}for (int i = 0; i < handlers.size(); i++) {BizOrderHandler handler = handlers.get(i);BizOrderHandler nextHandler = i + 1 < handlerNameChain.size() ? handlers.get(i + 1) : null;handler.setNextBizOrderHandler(nextHandler);}return handlers.get(0);
}public static void main(String[] args) {BizOrderHandler handler1 = initHandlerPro(Lists.newArrayList("Storage", "Payment", "RightCenter", "PointDeduction"));BizOrderHandler handler2 = initHandlerPro(Lists.newArrayList("Storage", "RightCenter", "PointDeduction"));ProductVO productVO = ProductVO.builder().build();Result result = handler1.handle(productVO);System.out.println("订单处理 成功--租户1");result = handler2.handle(productVO);System.out.println("订单处理 成功--租户2");
}

执行代码:

图片

好了问题完美解决,现在多租户也支持了。上线搞定这次需求

5、第三次迭代

产品又又来了,提了一个新的需求

产品说,我们需要支持条件判断,租户A要求,权益扣减和积分扣减必须全部成功完成一个就可以进入支付环节,不必都要把权益扣减和积分扣减流程走一遍

分析一下这个需求权益扣减和积分扣减都要完成才可以进入支付环节 当然最简单的改法是在权益和积分环节做个判断,要是失败了就跳出责任链,但是假如产品经理下次又说权益扣减和积分扣减完成一个就能进入支付,我们还得修改这个权益和积分实现里面的判断,频繁修改实现可并不是好事。

那咱们可以考虑代理模式,熟悉网关的都知道网关其实就是一个大代理,咱们按照这种思想可以搞一个网关代理权益扣减和积分扣减环节。于是咱们搞出来一个“网关”组件

@Data
public class BizOrderHandlerUnionGateway extends BizOrderHandler {List<BizOrderHandler> proxyHandlers;@Overridepublic Result handle(ProductVO param) {boolean isAllSuccess = true;if (proxyHandlers != null) {for (BizOrderHandler handler : proxyHandlers) {Result result = handler.handle(param);if (result.isSuccess()) {// 一个代理执行器 执行成功 则继续执行continue;} else {isAllSuccess = false;break;}}}if (isAllSuccess) {return super.next(param);}else{throw new RuntimeException("execute Failed");}}
}

上面的网关叫做union网关也就是并集网关,也就是说代理的处理器全部都执行成功才继续传递责任链,需要注意的是这个类也是BizOrderHandler的一个实现,只不过它的内部没有逻辑,只是对proxyHandlers中的组件进行代理。

然后简单修改下工厂 加一个分支:

public static BizOrderHandler buildBizOrderHandler(String bizType) {switch (bizType) {case "Storage":return new StorageCheckBizOrderHandler();case "Payment":return new PaymentBizOrderHandler();case "RightCenter":return new RightCenterBizOrderHandler();case "PointDeduction":return new PointDeductBizOrderHandler();case "UnionGateway":return new BizOrderHandlerUnionGateway();default:return null;}
}

然后我们用下面的方法获取首个执行节点,就可以执行整个责任链了:

private static BizOrderHandler initHandlerWithGateway() {BizOrderHandler storage = BizOrderHandlerFactory.buildBizOrderHandler("Storage");BizOrderHandler payment = BizOrderHandlerFactory.buildBizOrderHandler("Payment");BizOrderHandler rightCenter = BizOrderHandlerFactory.buildBizOrderHandler("RightCenter");BizOrderHandler pointDeduction = BizOrderHandlerFactory.buildBizOrderHandler("PointDeduction");BizOrderHandlerUnionGateway unionGateway = (BizOrderHandlerUnionGateway) BizOrderHandlerFactory.buildBizOrderHandler("UnionGateway");storage.setNextBizOrderHandler(unionGateway);unionGateway.setNextBizOrderHandler(payment);// unionGateway 加入责任链,权益和积分交给这个uniongateway进行代理控制unionGateway.setProxyHandlers(Lists.newArrayList(rightCenter, pointDeduction));return storage;
}

6、第四次迭代

产品又又又来了,这次提了一个技术需求

用户反馈生产订单流接口响应过慢,页面卡顿,观察接口发现目前的订单流程需要走的链路比较冗长,虽然用了责任链模式但本质上代码执行仍然是同步的,导致一个订单流完成耗费的时间过长,现在希望订单流接口异步化,然后需要发挥分布式部署的优势,每一个环节可以单独分散到每个单个部署节点上执行。

这次我们发现问题需要异步化还要分布式,这怎么办,显然简单的内存责任链不行了,咱们得上升到分布式责任链模式的方式,那怎么实现分布式责任链呢,咱们可以借助MQ来实现消息触发,于是观察者模式上线,这次咱们借助观察者模式的思想彻底完成分布式重构。

ps:果然需求演进的最后就是重构,不重构没有KPI。

咱们首先定义一个事件,这个就是订单流事件:

@Data
public class OrderFlowEvent implements Serializable {private String orderNo;private String currentFlow;private String nextFlow;}

这个事件可以在订单流发起的时候丢到消息队列里面,然后就可以进行订单流的流转了,下面我们来看消息处理逻辑,咱们使用模板方法再次进行一次代码优化,这里还是一个抽象类,然后我们的,支付、权益、积分只需要实现这个抽象类实现handleEvent逻辑就可以了,此外我们只用一个Topic,当前环节处理完成之后如果还有后续流程则再次发送消息到消息队列,进行下一步处理,此外handlerMap 代表责任链名称和责任链处理器的对应关系,handlerChain则是责任链的环节配置。

@Data
public abstract class BizHandler {String topicName = "biz_handle_topic";Map<String, BizHandler> handlerMap = new HashMap<>();Map<String, String> handlerChain = new LinkedHashMap<>();/*** 模板方法:在收到订单流的消息之后将进到这里进行业务逻辑处理** @param msg 订单流消息*/public void handle(String msg) {if (CollectionUtils.isEmpty(handlerMap) || CollectionUtils.isEmpty(handlerChain)) {//log.warn("handlerMap or handlerChain is empty");return;}OrderFlowEvent orderFlowEvent = JSON.parseObject(msg, OrderFlowEvent.class);String currentFlow = orderFlowEvent.getCurrentFlow();String nextFlow = handlerChain.get(currentFlow);// 当前环节的处理器进行业务处理Result result = handlerMap.get(currentFlow).handleEvent(orderFlowEvent);if (!result.isSuccess()) {throw new RuntimeException("handleException");}if (nextFlow == null) {return;}if (result.isSuccess()) {// 处理成功并且还有后续流程则再次向订单流Topic中发送消息sendFlowMsg(result.getData(), nextFlow, handlerChain.get(nextFlow));}}public abstract Result handleEvent(OrderFlowEvent orderFlowEvent);public void sendFlowMsg(Object data, String currentFlow, String nextFlow) {OrderFlowEvent orderFlowEvent = new OrderFlowEvent();orderFlowEvent.setCurrentFlow(currentFlow);orderFlowEvent.setNextFlow(nextFlow);MqUtils.sendMsg(topicName, JSON.toJSONString(orderFlowEvent));}}

例如仓储环节可以这样实现:

public class StorageBizHandler extends BizHandler {@Overridepublic Result handleEvent(OrderFlowEvent orderFlowEvent) {System.out.println("StorageBizHandler handle orderFlowEvent=" + JSON.toJSONString(orderFlowEvent));return Result.success();}
}

使用的时候则可以这样:

public class OrderFlowClient {void handleOrder() {// 定义一下流程名称和流程实例的对应关系Map<String, BizHandler> handlerMap = new HashMap<>();handlerMap.put("Storage", new StorageBizHandler());handlerMap.put("PointDeduction", new PointDeductionBizHandler());handlerMap.put("Payment", new PaymentBizHandler());//注意这里用LinkedHashMap 保证顺序 key标识当前流程 value标识下一个流程Map<String, String> handlerChain = new LinkedHashMap<>();handlerChain.put("Storage", "PointDeduction");handlerChain.put("PointDeduction", "Payment");handlerChain.put("Payment", null);// 开启分布式订单流转Map.Entry<String, String> first = handlerChain.entrySet().iterator().next();String key = first.getKey();OrderFlowEvent orderFlowEvent = new OrderFlowEvent();orderFlowEvent.setCurrentFlow("Storage");orderFlowEvent.setOrderNo("order001123124123");handlerMap.get(key).handle(JSON.toJSONString(orderFlowEvent));}}

最后咱们完成了这次大的技术演进需求,但是就到此结束了吗?按照这种设计思路改动之后你发现分布式环境下各种并发问题又出现了,于是你还需要分布式锁来控制,有了分布式锁你发现环节失败了还得引入重试逻辑,重试应该怎么设计,所以发现到了分布式系统下问题变得复杂了,还得继续想办法一个个攻克。

6、总结

本文通过一次简单的需求演进分别讲述了责任链、模板方法、策略模式、工厂模式、代理模式、观察者模式的使用,通过实际场景介绍下不同需求下如何通过适合的设计模式来解决问题。

最后说一句(求关注!别白嫖!)

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/730901.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HNU-算法设计与分析-甘晴void学习感悟

前言 算法设计与分析&#xff0c;仅就课程而言&#xff0c;似乎是数据结构与算法分析的延续 教材使用&#xff1a; 课程 关于课程&#xff0c;橙学长讲的非常清晰&#xff0c;我深以为然。 HNUCS-大三课程概览-CSDN博客文章浏览阅读1.3k次&#xff0c;点赞5次&#xff0c;收…

安装nginx:手动安装和yum安装

本文在centos7.9下分别尝试了yum安装和手动安装&#xff0c;记录一下试验过程。为后来者少踩点坑。 下载 下载地址&#xff1a;链接 。建议下载稳定版本&#xff0c;也就是Stable Version&#xff0c;这里下载的是 nginx-1.24.0 # 我下载在如下文件夹 mkdir/opt/apps cd /op…

ES入门五:组合查询

带有组合功能的Api有以下几个&#xff1a; Bool Query&#xff1a;布尔查询&#xff0c;可以组合多个过滤语句来过滤文档Boosting Query&#xff1a;在postive块中指定匹配文档的语句&#xff0c;同时降低在negative块中也匹配的文档的得分&#xff0c;提供调整相关性算法的能…

代码随想录 回溯算法-排序

目录 46.全排序 47.全排列|| 332.重新安排行程 46.全排序 46. 全排列 中等 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,…

蓝桥杯每日一题:烤鸡dfs

这道题考察了dfs的应用&#xff0c;题干十分有趣&#xff0c;思考过程对以后类似题目也有很强的参考性&#xff0c;一起来学习吧&#xff01; 题目&#xff1a; # 烤鸡 ## 题目背景 猪猪 Hanke 得到了一只鸡。 ## 题目描述 猪猪 Hanke 特别喜欢吃烤鸡&#xff08;本是同畜…

蓝桥杯练习题——归并排序

1.火柴排队 思路 1.求最小值的时候&#xff0c;可以直接按升序排序&#xff0c;这样得到的值就是最小值 2.求最小交换次数的时候&#xff0c;不能直接排序&#xff0c;因为只能交换相邻的数&#xff0c;只需要知道他们的相对大小&#xff0c;所以可以先用离散化&#xff0c;把…

清华大学1748页CTF竞赛入门指南,完整版开放下载!

CTF是一种针对信息安全领域的经济性挑战&#xff0c;旨在通过解决一系列的难题来寻找隐藏的“flag”。CTF比赛战队一般是以高校、科研单位、企业、信息安全从业者或社会团体组成。对于网安爱好者及从业者来说&#xff0c;拥有“CTF参赛经验”也是求职中的加分项。 前几天分享的…

什么是智慧公厕?智慧公厕设备有哪些

在现代社会&#xff0c;公共厕所作为城市基础设施的重要一环&#xff0c;承载着城市卫生、居民生活品质的重要责任。然而&#xff0c;传统公厕存在的问题仍然不可忽视&#xff1a;脏乱差、资源浪费、安全隐患等等。 为了解决这些问题&#xff0c;针对公共厕所日常使用、运营管…

六、长短时记忆网络语言模型(LSTM)

为了解决深度神经网络中的梯度消失问题&#xff0c;提出了一种特殊的RNN模型——长短期记忆网络&#xff08;Long Short-Term Memory networks, LSTM&#xff09;&#xff0c;能够有效的传递和表达长时间序列中的信息并且不会导致长时间前的有用信息被忽略。 长短时记忆网络原理…

ORA/GSA -- 学习记录

brief over-representation analysis(ORA),过表“达”分析&#xff0c;就是我们做多分组的RNAseq数据解析后会得到一些差异表达的gene&#xff0c;有些时候是单独拿出一个差异gene去解释表型&#xff0c;缺点是欠缺证据力度。有些人就把一些相关的差异gene放在一块儿解释&…

网络编程---网络编程入门、UDP通信程序、TCP通信程序

1.网络编程入门 1.网络编程概述 网络编程&#xff1a; 在网络通信协议下&#xff0c;实现网络互连的不同计算机上运行的程序间可以进行数据传输 计算机网络&#xff1a; 是指将地理位置不同的具有独立功能的多台计算机及其外部设备&#xff0c;通过通信线路连接起来&#…

J1周-ResNet-50算法

本文为&#x1f517;365天深度学习训练营 中的学习记录博客 原作者&#xff1a;K同学啊|接辅导、项目定制 我的环境&#xff1a; 1.语言&#xff1a;python3.7 2.编译器&#xff1a;pycharm 3.深度学习框架Tensorflow/Pytorch 1.8.0cu111 一、问题引出 CNN能够提取低、中、…

qnx启动中控屏黑屏

bmetrics_service boot metrics service, 用于记录统计启动性能信息,读取/dev/bmetrics可以获取到这些信息 # use memorydump memorydump Sets the debug cookies, copies MMU info into reset_info asinfo, sets the secure monitor(TZ) dump buffer, starts tracelogger Usa…

VR全景技术在VR看房中有哪些应用,能带来哪些好处

引言&#xff1a; 随着科技的不断发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术在房地产行业中的应用也越来越广泛。其中&#xff0c;VR全景技术在VR看房中的运用尤为突出。今天&#xff0c;让我们一起深入探讨VR全景技术在VR看房中的应用及其带来的种种好处。 一、…

ES入门二:文档的基本操作

索引管理 创建索引 删除索引 文档管理 创建文档 如果有更新需求&#xff0c;使用第一种如果有唯一性校验&#xff0c;使用第二种如果需要系统给你创建文档Id&#xff0c;使用第三种 &#xff08;这个性能更好&#xff09; 相比第一种&#xff0c;第三种的写入效率更高&#xf…

基础GamePlay知识-碰撞检测

将会持续更新gameplay的一些基础知识&#xff0c;一同学习。 扇形检测 扇形检测是Gameplay里面很常见的场景。比如荒野乱斗中&#xff0c;大部分的近战角色都是扇形攻击。在扇形范围内就认为是受击。 扇形检测只有两个参数&#xff0c;一个是扇形的角度一个是扇形的半径大小。…

直播预告|小白开箱: 云数据库在五朵云上的评测

3 月 7 日&#xff0c;周四晚上 19:00-20:30 由明说三人行组织&#xff0c;邀请了 NineData 国际总经理(GM) Ni Demai、云猿生数据 CTO &#xff06; 联合创始人子嘉&#xff0c;和《明说三人行》创始人 &主持人明叔&#xff0c;共同围绕《小白开箱: 云数据库在五朵云上的评…

官网正在被哪些产品蚕食,定制网站又被哪些建站产品挤占。

2023-12-09 16:22贝格前端工场 官网建设是一个被大多数人看衰的市场&#xff0c;本文来理性分析下&#xff0c;谁在蚕食这个市场&#xff0c;谁又在挤占这个产品生存空间&#xff0c;欢迎大家评论&#xff0c;探讨。 网站正在被以下产品形式取代&#xff1a; 1. 移动应用&…

揭秘Web缓存:提升网站性能与用户体验

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

金融数据采集与风险管理:Open-Spider工具的应用与实践

一、项目介绍 在当今快速发展的金融行业中&#xff0c;新的金融产品和服务层出不穷&#xff0c;为银行业务带来了巨大的机遇和挑战。为了帮助银行员工更好地应对这些挑战&#xff0c;我们曾成功实施了一个创新的项目&#xff0c;该项目采用了先进的爬虫技术&#xff0c;通过ope…