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

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.hqwc.cn/news/522444.html

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

相关文章

Linux--搭建Zabbix监控系统

11.1 案例分析 要想实时地了解服务器的运行状况并且能在出现问题时及时解决&#xff0c;利用监控软件是一个很好的 途径。 Zabbix&#xff08;免费的&#xff09;是一个基于Web界面的企业级开源监控套件&#xff0c;提供分布式系统监控与网络监视功能。具备主机的性能监控。网络…

对simplex算法的时间复杂度进行分析

对于simplex算法,如果每进行一次pivot变换,目标函数所得到的结果都会有可能出现增加的情况,所以得到的结论中,可以肯定它的值是一定不会出现减少的情况的,每次从目标函数中找到一个系数大于0的变量,然后再在约束条件中选取能够让它的增值最少的那个来继续进行pivot变换。…

egg如何写单元测试

优秀的代码需要有单元测试进行质量保证&#xff0c;每个测试用例都给应用的稳定性提供了一层保障。 测试目录结构 我们约定 test 目录为存放所有测试脚本的目录&#xff0c;测试所使用到的 fixtures 和相关辅助脚本都应该放在此目录下。 测试文件的目录和我们需要测试的文件目…

考研数学|张宇30讲,搭配什么基础题?

如果基础跟的是张宇&#xff0c;那么基础做的题目要根据自己的题目来决定 题集的选择最好不要太难&#xff0c;而且基础也不用做太多题目&#xff0c;以数学知识点的运用&#xff0c;培养做题感觉为主。 张宇老师的课程在基础阶段也有配套的课程&#xff0c;就是《张宇基础30…

力扣hot100:438.找到字符串中所有字母异位词(滑动窗口)

26个字符&#xff0c;我复制怎么了&#xff1f;26个字符我比较个数怎么了&#xff1f; 顶多时间复杂度*26 本题用固定窗口大小的滑动窗口每次比较包含26个元素的数组次数&#xff0c;最容易写。 动态窗口大小哈希表存数值&#xff08;双指针差值&#xff09;难想难写。 一、动态…

HTML—常用标签

常用标签&#xff1a; 标题标签&#xff1a;<h1></h1>......<h6></h6>段落标签&#xff1a;<p></p>换行标签&#xff1a;<br/>列表&#xff1a;无序列表<ul><li></li></ul> 有序列表<ol>&…

React-Redux中actions

一、同步actions 1.概念 说明&#xff1a;在reducers的同步修改方法中添加action对象参数&#xff0c;在调用actionCreater的时候传递参数&#xff0c;数会被传递到action对象payload属性上。 2.reducers对象 说明&#xff1a;声明函数同时接受参数 const counterStorecre…

python基础篇--学习记录2

1.深浅拷贝 l1 ["张大仙","徐凤年",["李淳刚","邓太阿"]] # 变量名对应的就是内存地址,这里就是将l1的内存地址给了l2 # 现在两个变量指向同一个内存地址,l1变化l2也会变化 l2 l1 现在的需求是l2是l1的拷贝版本,但是两者是完全分割…

【C++】C++模板基础知识篇

个人主页 &#xff1a; zxctscl 文章封面来自&#xff1a;艺术家–贤海林 如有转载请先通知 文章目录 1. 泛型编程2. 函数模板2.1 函数模板概念2.2 函数模板格式2.3 函数模板的原理2.4 函数模板的实例化2.5 模板参数的匹配原则 3. 类模板3.1 类模板的定义格式3.2 类模板的实例化…

3DES算法的起源与演进:保障信息安全的重要里程碑

title: 3DES算法的起源与演进&#xff1a;保障信息安全的重要里程碑 date: 2024/3/8 21:25:19 updated: 2024/3/8 21:25:19 tags: 3DES算法起源安全性增强三次迭代加密密钥管理复杂效率对比AES应用场景广泛Python实现示例 一、3DES算法的起源与演进 3DES算法是DES算法的增强版…

Golang基于Redis bitmap实现布隆过滤器(完结版)

Golang基于Redis bitmap实现布隆过滤器&#xff08;完结版&#xff09; 为了防止黑客恶意刷接口&#xff08;请求压根不存在的数据&#xff09;&#xff0c;目前通常有以下几种做法&#xff1a; 限制IP&#xff08;限流&#xff09;Redis缓存不存在的key布隆过滤器挡在Redis前 …

Linux运维:实现光盘开机自动挂载、配置本地yum源教程

Linux运维&#xff1a;实现光盘开机自动挂载、配置本地yum源教程 一、光盘开机自动挂载1、检查光驱设备2、创建挂载点3、编辑/etc/fstab文件4、测试挂载 二、配置本地yum源(挂载光盘或ISO文件)1、挂载ISO文件2、创建YUM仓库配置文件3、清理YUM缓存并测试 &#x1f496;The Begi…