写出优雅的业务代码(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<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被赋值

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

后记

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

  1. 指定策略类们的包路径(当然,所有的策略类都需要放在同一个路径下)。
  2. 然后去扫描该路径下的所有类并且可以拿到全限定名。
  3. 通过反射可以生成所有的策略类,add到集合里。
  4. 实现起来还会有一些细节需要考虑。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342