前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >复杂业务场景下,如何优雅的使用设计模式来优化代码?

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

原创
作者头像
程序员蜗牛
发布2024-03-08 21:30:45
1210
发布2024-03-08 21:30:45

1、引言

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

2、背景

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

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

3、第一次迭代

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

先整体看下类图:

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

代码语言:javascript
复制
@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);
????}

}

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

代码语言:javascript
复制
public?class?StorageCheckBizOrderHandler?extends?BizOrderHandler?{
????@Override
????public?Result?handle(ProductVO?param)?{
????????//?这里写上仓储管理的业务逻辑
????????System.out.println("StorageCheckBizOrderHandler?doing?business!");
????????return?super.next(param);
????}
}

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

代码语言:javascript
复制
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:仓储检查->积分扣减->权益扣减

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

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

代码语言:javascript
复制
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里面存放的实例全部是单例,搞出来了环形链表了....

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

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

代码语言:javascript
复制
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中取实例,转为从工厂方法里面获取实例:

代码语言:javascript
复制
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要求,权益扣减和积分扣减必须全部成功完成一个就可以进入支付环节,不必都要把权益扣减和积分扣减流程走一遍

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

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

代码语言:javascript
复制
@Data
public?class?BizOrderHandlerUnionGateway?extends?BizOrderHandler?{
????List<BizOrderHandler>?proxyHandlers;

????@Override
????public?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中的组件进行代理。

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

代码语言:javascript
复制
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;
????}
}

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

代码语言:javascript
复制
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。

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

代码语言:javascript
复制
@Data
public?class?OrderFlowEvent?implements?Serializable?{

????private?String?orderNo;

????private?String?currentFlow;

????private?String?nextFlow;

}

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

代码语言:javascript
复制
@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));
????}


}

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

代码语言:javascript
复制
public?class?StorageBizHandler?extends?BizHandler?{

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

使用的时候则可以这样:

代码语言:javascript
复制
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、总结

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

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

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、引言
  • 2、背景
  • 3、第一次迭代
  • 4、第二次迭代
  • 5、第三次迭代
  • 6、第四次迭代
  • 6、总结
相关产品与服务
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com