关键字:
- 如何写好业务代码
- 如何用好spring framework
- switch case语句
- 设计模式
- 工厂模式
- 业务架构
本文概要:
对于做web开发的java程序员来说,如何写出更好看的业务代码。
本文会展示利用spring framework中的自动注入Collections,来替换掉工厂模式中可能会用到的switch case,从而达到代码设计中的开闭原则。
业务简介:
开始之前先了解一下现状。
- 业务是通过调用支付宝接口来做支付订单。
- 业务中有10种订单类型。
- 通过接口参数里的payType参数确定是哪种订单,然后执行对应的订单分支逻辑,调用阿里支付,返回交易编号。
代码现状
优化这部分业务主要是通过【模版方法模式】,【策略模式】,【工厂模式】来解决重复编码,与多种订单策略的开闭原则问题。详情可见:《写出优雅的业务代码(1):项目中的模版方法,策略模式》文章。该片文章里已经做了一次优化,不过如何决定使用哪个订单策略,还需要使用策略工厂OrderStrategyFactory
通过传入参数的PayType,使用switch case语句来判断。代码如下:
@Component
public class OrderStrategyFactory {
@Autowired
private ActivityOrderStrategy activityOrderStrategy;
@Autowired
private GroupbuyOrderStrategy groupbuyOrderStrategy;
@Autowired
private OrgOrderObjStrategy orgOrderObjStrategy;
@Autowired
private OrgOrderRewardStrategy orgOrderRewardStrategy;
@Autowired
private OrgOrderServiceStrategy orgOrderServiceStrategy;
@Autowired
private ShopOrderStrategy shopOrderStrategy;
@Autowired
private ShopVoucherStrategy shopVoucherStrategy;
@Autowired
private YxOrderStrategy yxOrderStrategy;
public IOrderAlipayStrategy createOrderInstance(PayType payType) {
IOrderAlipayStrategy orderAlipayStrategy = null;
switch (payType) {
case ORG_ORDER_SERVICE: // DONE
orderAlipayStrategy = orgOrderServiceStrategy;
break;
case ORG_ORDER_OBJ: // DONE
orderAlipayStrategy = orgOrderObjStrategy;
break;
case ORG_ORDER_REWAED: // DONE
orderAlipayStrategy = orgOrderRewardStrategy;
break;
case GROUPBUY_ORDER: // DONE
orderAlipayStrategy = groupbuyOrderStrategy;
break;
case SHOP_ORDER: // DONE
orderAlipayStrategy = shopOrderStrategy;
break;
case SHOP_VOUCHER: // DONE
orderAlipayStrategy = shopVoucherStrategy;
break;
case YX_ORDER: // DONE
orderAlipayStrategy = yxOrderStrategy;
break;
case ACTIVITY_ORDER: // DONE
orderAlipayStrategy = activityOrderStrategy;
break;
default:
}
return orderAlipayStrategy;
}
}
有了这个订单策略工厂类,就可以在需要用到订单策略类的地方,传入PayType来获取到对应的订单策略类,代码如下:
// 通过策略工厂取得具体订单策略类
IOrderAlipayStrategy orderAlipayStrategy = orderStrategyFactory.createOrderInstance(payType);
好,看起来一切都正常,功能可以完成。还有一个工厂,也用到了设计模式。
下面来考虑一个问题:
如果业务里增加了一种订单类型,比如:大头娃娃订单类型。那么按照目前的代码,应该如何做?似乎顺理成章的会按如下做法操作:
- 增加一个【大头娃娃订单策略类】;
- 再在枚举类PayType里增加一个
BigHeadBaby
类型; - 在工厂类OrderStrategyFactory里的switch case语句中添加一个case,如下:
@Autowired
private 大头娃娃订单策略 bigHeadBabyStrategy;
...
switch(payType) {
...
case BigHeadBaby:
orderAlipayStrategy = bigHeadBabyStrategy
...
}
好了,这样是可以完成任务的,代码的改动点如下:
- 增加一个策略类
- 修改工厂方法(违反了开闭原则)
那么,问题来了:如果我在修改工厂方法的时候,手抖了,或者手欠了把其它代码修改了怎么办呢?如果有语法错误还好,ide能提示,编译也会通不过,但是如果误操作导致的问题恰好没有语法错误,这时就可能将问题代码带到线上,从而影响到其它订单类型的正确工作了。这就是为什么要有开闭原则了。
问题抛出来了,下面来解决问题。
优化掉switch case语句
先说一下目标:
当增加订单策略的时候,不需要修改策略工厂类,策略工厂类自始自终不会被修改。这点很重要。
再说下思路:
- 策略类的接口中增加一个判断策略类类型的方法
PayType getPayType();
,具体的策略类去实现这个方法,并且返回自身所属的PayType类型 - 将所有策略类都放入一个集合;
- 在工厂类中,遍历这个集合,调用
getPayType()
方法,返回符合要求的策略类;
先看下策略类接口,策略类 与 工厂方法的代码实现
- 策略类接口,请关注第一个方法
PayType getPayType();
public interface IOrderAlipayStrategy {
/**
* 这是增加的,为了返回自己是哪个策略的方法
* 不需要参数
* @return
*/
PayType getPayType();
/**
* 通过alipay支付,OrderInfo,包含阿里支付返回的TradeNo
*/
OrderInfo payThroughAlipay(AlipayConfig alipayConfig, PayType payType, String orderId, String alipayUserId) throws Exception;
}
- 策略类实现类,请关注同样是
public PayType getPayType()
方法,以及方法返回值。该方法返回的是枚举类型PayType中的ORG_ORDER_SERVICE,告诉调用者它是ORG_ORDER_SERVICE订单。
/**
* @Author: yesiming
* @Platform: Mac
* @Date: 5:13 下午 2020/9/25
*
* 员工订单服务费
*/
@Component
public class OrgOrderServiceStrategy extends AbstractOrderAlipayStrategy {
@Autowired
private OrgOrderMapper orgOrderMapper;
@Override
public PayType getPayType() {
return ORG_ORDER_SERVICE;
}
/**
* 重写了模版类中的业务相关方法,实现业务的独立性
* @param orderId
* @param payType
* @return
*/
@Override
public OrderInfo prepareArgs(String orderId, PayType payType) {
OrgOrder orgOrderService = orgOrderMapper.getById(Long.parseLong(orderId));
BigDecimal payAmount = orgOrderService.getTotalServiceCostAmount();
OrderInfo orderInfo = new OrderInfo(payAmount,orgOrderService.getTypeName(), payType, orgOrderService.getOrderNo());
return orderInfo;
}
}
- 工厂类代码,请看注释。
@Component
public class OrderStrategyFactory {
// 用这个list来装载所有的策略类
@Autowired
private List<IOrderAlipayStrategy> orderStrategyList;
/**
* 遍历策略类集合,取出属于与传入参数payType类型的策略类
*/
public IOrderAlipayStrategy getOrderInstance(PayType payType) {
for(IOrderAlipayStrategy orderAlipayStrategy : orderStrategyList) {
if (orderAlipayStrategy.getPayType() == payType) {
return orderAlipayStrategy;
}
}
return null;
}
}
回头分析一下上述代码
上述代码并不难,重点是如何将所有的策略类都放入集合orderStrategyList
,那么时候否放入?以何种方式放入?这是需要思考的问题。
按照所学过的java知识,可以在static
代码块中初始化执行一段代码,那么可以将搜罗到的策略类new完之后,统统add
进入orderStrategyList
。
但项目是机遇springframework的,策略类中势必会有依赖于springframework的操作,也就是说会有基于springframework的依赖注入或者其它特性的操作,如果直接new出来策略类,这些springframework的特性将会不起作用,也就无法完成策略类中属性的依赖注入。除非策略类里用到的属性不用springframework的依赖注入,这显然不好。
如何让orderStrategyList被赋值
- 可行的做法:
springframework提供了对于Collections类型的依赖注入,写法与普通属性的写法一样,通过加@Autowired
注解即可,工厂类中的代码片段如下:
@Autowired
private List<IOrderAlipayStrategy> orderStrategyList;
- 下面执行代码观察一下结果
通过下图可以看到,当调用到工厂类的getOrderInstance()
方法时,属性orderStrategyList
是有值的,而且元素内容都是接口IOrderAlipayStrategy
的非抽象实现类的对象。
- 稍稍解释一下
上面代码的作用,是在spring初始化的阶段,通过 getBeanNamesForType 的方式得到集合类型范型相匹配的类们的信息,后续再注入集合变量里;也就是说,会将系统内的所有实现了IOrderAlipayStrategy
接口的非抽象类对象实例都add
到orderStrategyList
中。具体的源码分析请看《spring里【集合类型属性】的注入》
后记
如果项目不基于spring框架去做,那么如何做到增加策略类后,自动的就能够被加载到策略类集合属性里呢?
下述是一种做法:
- 指定策略类们的包路径(当然,所有的策略类都需要放在同一个路径下)。
- 然后去扫描该路径下的所有类并且可以拿到全限定名。
- 通过反射可以生成所有的策略类,add到集合里。
- 实现起来还会有一些细节需要考虑。