写出优雅的业务代码(2):优化掉工厂模式中的 switch case

关键字:

  1. 如何写好业务代码
  2. 如何用好spring framework
  3. switch case语句
  4. 设计模式
  5. 工厂模式
  6. 业务架构

本文概要:

对于做web开发的java程序员来说,如何写出更好看的业务代码。
本文会展示利用spring framework中的自动注入Collections,来替换掉工厂模式中可能会用到的switch case,从而达到代码设计中的开闭原则。

业务简介:

开始之前先了解一下现状。

  1. 业务是通过调用支付宝接口来做支付订单。
  2. 业务中有10种订单类型。
  3. 通过接口参数里的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);

好,看起来一切都正常,功能可以完成。还有一个工厂,也用到了设计模式。
下面来考虑一个问题:
如果业务里增加了一种订单类型,比如:大头娃娃订单类型。那么按照目前的代码,应该如何做?似乎顺理成章的会按如下做法操作:

  1. 增加一个【大头娃娃订单策略类】;
  2. 再在枚举类PayType里增加一个BigHeadBaby类型;
  3. 在工厂类OrderStrategyFactory里的switch case语句中添加一个case,如下:
@Autowired
private 大头娃娃订单策略 bigHeadBabyStrategy;
...
switch(payType) {
...
  case BigHeadBaby: 
      orderAlipayStrategy = bigHeadBabyStrategy
...
}

好了,这样是可以完成任务的,代码的改动点如下:

  1. 增加一个策略类
  2. 修改工厂方法(违反了开闭原则)

那么,问题来了:如果我在修改工厂方法的时候,手抖了,或者手欠了把其它代码修改了怎么办呢?如果有语法错误还好,ide能提示,编译也会通不过,但是如果误操作导致的问题恰好没有语法错误,这时就可能将问题代码带到线上,从而影响到其它订单类型的正确工作了。这就是为什么要有开闭原则了。

问题抛出来了,下面来解决问题。

优化掉switch case语句

先说一下目标:

当增加订单策略的时候,不需要修改策略工厂类,策略工厂类自始自终不会被修改。这点很重要。

再说下思路:

  1. 策略类的接口中增加一个判断策略类类型的方法PayType getPayType();,具体的策略类去实现这个方法,并且返回自身所属的PayType类型
  2. 将所有策略类都放入一个集合;
  3. 在工厂类中,遍历这个集合,调用getPayType()方法,返回符合要求的策略类;

先看下策略类接口,策略类 与 工厂方法的代码实现

  1. 策略类接口,请关注第一个方法PayType getPayType();
public interface IOrderAlipayStrategy {

    /**
     * 这是增加的,为了返回自己是哪个策略的方法
     * 不需要参数
     * @return
     */
    PayType getPayType();

    /**
     * 通过alipay支付,OrderInfo,包含阿里支付返回的TradeNo
     */
    OrderInfo payThroughAlipay(AlipayConfig alipayConfig,  PayType payType, String orderId, String alipayUserId) throws Exception;
}
  1. 策略类实现类,请关注同样是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;
    }
}
  1. 工厂类代码,请看注释。
@Component
public class OrderStrategyFactory {

    // 用这个list来装载所有的策略类
    @Autowired
    private List 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被赋值

  1. 可行的做法:
    springframework提供了对于Collections类型的依赖注入,写法与普通属性的写法一样,通过加@Autowired注解即可,工厂类中的代码片段如下:
@Autowired
private List orderStrategyList;
  1. 下面执行代码观察一下结果
    通过下图可以看到,当调用到工厂类的getOrderInstance()方法时,属性orderStrategyList是有值的,而且元素内容都是接口IOrderAlipayStrategy的非抽象实现类的对象。
    写出优雅的业务代码(2):优化掉工厂模式中的 switch case_第1张图片
    调用工厂的getOrderInstance()方法
  2. 稍稍解释一下
    上面代码的作用,是在spring初始化的阶段,通过 getBeanNamesForType 的方式得到集合类型范型相匹配的类们的信息,后续再注入集合变量里;也就是说,会将系统内的所有实现了IOrderAlipayStrategy接口的非抽象类对象实例都addorderStrategyList中。具体的源码分析请看《spring里【集合类型属性】的注入》

后记

如果项目不基于spring框架去做,那么如何做到增加策略类后,自动的就能够被加载到策略类集合属性里呢?
下述是一种做法:

  1. 指定策略类们的包路径(当然,所有的策略类都需要放在同一个路径下)。
  2. 然后去扫描该路径下的所有类并且可以拿到全限定名。
  3. 通过反射可以生成所有的策略类,add到集合里。
  4. 实现起来还会有一些细节需要考虑。

你可能感兴趣的:(写出优雅的业务代码(2):优化掉工厂模式中的 switch case)