最近这一周的工作,是重构一个退款需求。
背景
我负责的一个电商app的订单模块,因为随着业务的不断发展,出现各种各样的订单类型。
订单维度
比如,有普通订单,团购订单,组合订单,服务卡服务订单等各种订单
不同的订单,有不同的退款逻辑
比如,普通订单,退款时,需要退款给C端用户,修改订单状态,是否有空跑费
然后空跑费也因不同的付款模式有不同的计算方式。
比如,一段支付,根据订单金额的一定比例进行计算。上限是5RMB这样子
二段支付,根据订单的类型,固定扣10RMB这样子
用户维度
什么意思?
就是<typo id="typo-264" data-origin="不用" ignoretag="true">不用</typo>的用户,退款的逻辑也是不一样的。
比如,你是VIP用户,你退款,是立马就可以退的,比如第三方平台垫付给你。
假设你是普通用户,你退款时,就可能需要2个工作日内返回。
你用过淘宝的退款,应该就清楚了。你到达一定级别后,你退款是淘宝垫付给你的,并不是商家退给你的。等商家真正确认退款后,商家把钱给淘宝就好。
我们这里,需要结合用户维度+订单维度。
以前的退款逻辑,是写给普通订单的。比如是A RD写的
后来又增加了团购订单,此时又要增加一段新的退款逻辑。比如是B RD写的
后来的后来,又增加了组合订单,此时又要增加一段新的退款逻辑。比如C RD写的
后来的后来。。。。你懂的。。。。。
因为每个不同的RD理解的逻辑不一样,而且工期赶,就容易造成代码越来越混乱
这个时候必定要重构代码的。
设计方案
了解业务的基本逻辑后,我们需要<typo id="typo-641" data-origin="集合" ignoretag="true">集合</typo>用户维度和订单维度进行考虑
我当时心中就想到了策略模式。
下面我以订单维度进行分析
为什么用的策略模式呢?
因为每个订单不同的付款方式,导致了不同的退款逻辑。而策略模式的适用场景就是:
- 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
- 2、一个系统需要动态地在几种算法中选择一种。
- 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。(就是解决 if...else 所带来的复杂和难以维护)
用人话说就是:同一个行为,有不同的表现方式。
有没有觉得这个有点像多态,你觉得呢?
为什么用模板模式?
后来我继续深入了解退款的业务后,发现,其中有一些逻辑是公共的。
比如:订单的校验,更新退款表,计算空跑费
这个时候,其实就可以使用模板模式来解决。
模板模式的适用场景:
- 1、有多个子类共有的方法,且逻辑相同。
- 2、重要的、复杂的方法,可以考虑作为模板方法。
最后,我还使用了工厂模式,这个模式可用可不用都行,只不过既然优化了。
那干脆就用吧,用来当做一个容器:装载策略模式的实例
代码实战
首先,我们从退款入口开始:
策略模式
public void refund(OrderInfo refundVO) throws Exception {
try {
//订单校验
checkOrderInfo(refundVO);
//组装退款表的实体数据,落库
prepareDataAndInsertDataToDB(refundVO);
// 根据不同的用户使用不同的退款策略-->策略模式
IRefundUserStrategy refundUserStrategy = RefundUserStrategyFactory.getRefundUserStrategy(key);
refundUserStrategy.doRefundByUser();
}catch (Exception e){
log.error("C端用户退款发生异常:",e);
}
}
主要逻辑:
- 订单校验
- 组装退款表的实体数据,落库
- 根据不同的用户使用不同的退款策略
接下来,我们把目光聚焦到:RefundUserStrategyFactory
/**
* 用户退款策略工厂---工厂模式
*/
public class RefundUserStrategyFactory {
private static final HashMap<UserLevelEnum, IRefundUserStrategy> refudUserStrategyFactory = new HashMap<>();
public static IRefundUserStrategy getRefundUserStrategy(UserLevelEnum userLevelEnum) {
return refudUserStrategyFactory.get(userLevelEnum);
}
public static void putQueryStrategy(UserLevelEnum userLevelEnum, IRefundUserStrategy userStrategy) {
refudUserStrategyFactory.put(userLevelEnum, userStrategy);
}
}
说白了,这里的工厂,其实就是一个hashmap集合容器。
其实里面主要就2个方法,我来抽象一下:就是读和写2个方法。
读
读的时机:就是上述RefundUserStrategyFactory.getRefundUserStrategy(key)。什么时候用,就什么时候读就好。
写
那什么时候写进去呢?
当然是初始化对象后,这个时候需要结合到Spring的@PostConstruct注解
举个例子:
模板模式
@Component
@Slf4j
public class NormalUserRefundStrategy implements IRefundUserStrategy {
@PostConstruct
private void initStrategy() {
RefundUserStrategyFactory.putQueryStrategy(UserLevelEnum.NORMAL,this);
}
//普通用户的退款逻辑
doRefundByUser(){
//省略一堆代码
//调用不同的订单退款策略接口
AbstractOrderRefundTemplate orderRefundTemplate = orderRefundComp.getOrderRefundTemplate(key);
//这里根据取到的模板,来进行不同订单的退款策略--->模板模式
orderRefundTemplate.exeOrderRefund();
}
}
经过上面这样子折腾后。代码的逻辑会显得特别清晰:
订单校验
更新退款表
根据不同的用户采取不同的退款策略
根据不同的订单策略
总结
以后,新增新的订单类型时,只需要新增一具体的策略类就行。符合开闭原则。
上面就是用
工厂模式+策略模式+模板模式
来优化代码逻辑混乱的。
一般,你看到你代码逻辑中有很多if-else,都可以直接用这个策略来解决。
如果你有类似应用设计模式来优化代码问题,欢迎留言交流。