策略模式——一次真实的代码重构记录

背景简介

我目前所在的项目组,支付这一大块以前是根据业务流进行划分的,不同的业务流由不同的开发人员进行开发和管理。也就是说,A负责销售业务,B负责售后业务,C负责附加产品业务……那么新增一种支付方式,A、B、C就都要对这种支付方式进行开发。于是产生了不同业务对同一种支付方式的支持程度不一致的问题,比如销售业务已经支持微信和支付宝方式进行支付了,但是售后业务支付却还不支持。刚好最近接到了一个新的需求,需要支持一种新的支付方式。于是顺便把整个支付这块的代码进行了一次重构。

当然,整个重构还是有点复杂,本文主要还是着重于策略模式在本次支付场景的重构中的实际应用和起到的效果。

 

重构之前(本节可以选择性略过不看)

重构之前的代码差不多是这样的,为了方便理解,我把支付方式关键字换成了拼音。

if("1".equals(target)) { //国内销售的html表单支付模式(包括易宝、汇付....)

    if("YIBAO".equals(type)){
        SalePaymentService.doYibaoPay();  //国内销售的易宝支付
    } else if("YIBAO".equals(type)){
        SalePaymentService.doHuifuPay();  //国内销售的汇付支付
    }

    ......

} else if("2".equals(target)) { //国内销售的APP支付模式(包含微信、支付宝...)
    
    if("WEIXIN".equals(type)){
        SalePaymentService.doWeixinPay();  //国内销售的微信支付
    } else if("ZHIFUBAO".equals(type)){
        SalePaymentService.doZhifubaoPay();  //国内销售的支付宝支付
    }

    ......

} else if("3".equals(target)) { //国际销售的支付模式(包括国际外卡、银联卡...)
    if(***){
        ***********;
    } else if(***){
        ***********;
    }

    ......

} else if("4".equals(target)){ //所有的售后支付模式都从这里进入,里面再进行细分

    if("YIBAO".equals(type)){
        AfterSalePaymentService.doYibaoPay();  //售后的易宝支付
    } else if("YIBAO".equals(type)){
        AfterSalePaymentService.doHuifuPay();  //售后的汇付支付
    } else if("WEIXIN".equals(type)){
        AfterSalePaymentService.doWeixinPay();  //售后的微信支付
    } else if("ZHIFUBAO".equals(type)){
        AfterSalePaymentService.doZhifubaoPay();  //售后的支付宝支付
    }

} 

......

target和type参数是前端传过来的,用于区分业务范围和支付方式。重构之前由于不同开发负责不同的业务模块,所以开发的思路也不太一致。

负责销售模块开发的A同学喜欢“等级式管理”,于是有了国内支付和国际支付,国内国际下面细分了表单类支付,卡类支付,APP类支付,最后是最底层的各种具体的支付方式......

而负责售后的B同学就喜欢“扁平化管理”,直接一个入口进去之后就是各种具体的支付方式。

当然还会有负责其他业务的其他同学也都有各自的想法。可想而知,随着业务的不断增加和支付方式的不断增加,这个if-else将会发展成什么样的灾难。

 

运用策略模式进行重构(重点)

思路:重构前的代码有大量类似的if-else,很容易就想到用Map建立关键字和支付实现类的映射关系进行优化;不同的支付方式之间是互斥关系且根据业务需求会不断增加新的类型,这种场景一般会联想到策略模式。

接口类:PaymentService.java

public interface PaymentService {
    public OrderViewRS doPay(PaymentReq paymentReq);
}

实现类1-微信:Weixin.java

@Service
@PayTypeHandler("WEIXIN")  //这个注解后文解释
public class Weixin implements PaymentService{

    @Override
    public PaymentRes doPay(PaymentReq paymentReq){
        PaymentRes paymentRes = new PaymentRes();
        try {
            //入参校验
            validateParam(paymentReq);

            //具体支付逻辑
            paymentRes = specificPay(paymentReq);

        } catch (Exception e) {
            log.error("Payment system error:", e);
        }

        return paymentRes;
    }

实现类2-支付宝:Zhifubao.java

@Service
@PayTypeHandler("ZHIFUBAO") //这个注解后文解释
public class Zhifubao implements PaymentService{

    @Override
    public PaymentRes doPay(PaymentReq paymentReq){
        PaymentRes paymentRes = new PaymentRes();
        try {
            //入参校验
            validateParam(paymentReq);

            //具体支付逻辑
            paymentRes = specificPay(paymentReq);

        } catch (Exception e) {
            log.error("Payment system error:", e);
        }

        return paymentRes;
    }

策略模式的上下文类:PaymentServiceContext .java

@Component
public class PaymentServiceContext {
   private PaymentService paymentService;
 
   public PaymentServiceContext (PaymentService paymentService){
      this.paymentService= paymentService;
   }
 
   public int executePaymentStrategy(PaymentReq paymentReq){
      return paymentService.doPay(paymentReq);
   }

}

到此为止其实就已经用策略模式实现了整个支付的代码了。但是如果只是这样,需要很多的if-else才能根据type(支付类型)实例化对应实现类,也会引发策略模式典型的类膨胀(Bloating)问题。

于是我们前面说的Map就派上用场了。对PaymentServiceContext.java进行优化:

@Component
public class PaymentServiceContext {
   private final Map<String, PaymentService> paymentMap = new HashMap<>();
 
   public void putPaymentStrategy(String type, PaymentService paymentService) {
        paymentMap.put(type, paymentService);
    }

    public OrderViewRS executePayStrategy(String type,PaymentReq paymentReq){
        return paymentMap.get(type).doPay(paymentReq);
    }

}

进一步思考,每增加一种新的支付方式,我们就要手动把这个新的支付实现类put到paymentMap,这样还是很麻烦。有什么办法可以让系统将我需要的类加载到Map中呢?我想到的解决办法是借助自定义注解,在Spring启动时扫描自定义注解,并将自定义注解的value和对应的bean添加到paymentMap中。

自定义注解:PayTypeHandler.java

/**
 * 自定义支付注解
 * 为了让Spring启动时自动进行相关支付类加载
 */
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PayTypeHandler {
    String value();
}

Spring启动时自动获取所有支付bean保存到Map中 :PaymentContextAware.java

@Component
public class PaymentContextAware implements ApplicationContextAware  {
    @Override
    public void setApplicationContext(ApplicationContext applicationContext){
        //根据自定义PayTypeHandler注解取得所有使用此注解的bean
        Map<String,Object> beans = applicationContext.getBeansWithAnnotation(PayTypeHandler.class);
        //取得PayServiceContext的bean
        PaymentServiceContext paymentServiceContext = applicationContext.getBean(PaymentServiceContext.class);
        //把使用PayTypeHandler注解的bean存到PaymentServiceContext的Map中
        beans.forEach((name, bean) -> {
            PayTypeHandler payTypeHandler = bean.getClass().getAnnotation(PayTypeHandler.class);
            payServiceContext.putPaymentStrategy(payTypeHandler.value(),(PaymentService)bean);
        });
    }
}

模拟客户端调用:

@Component
public class PayStrategyDemo {
    @Autowired
    PaymentServiceContext paymentServiceContext;
    
    public static void main(String[] args) {
        String type = "WEIXIN";
        PaymentReq paymentReq = new PaymentReq();
        //省略参数封装
        return newPayServiceContext.executePayStrategy(type,paymentReq);
    }
}

 重构之后,相当于搭建了一个基本的支付框架,以后需要新增一种新的支付类型,只需要新建一个支付类实现PaymentService并且加上PayTypeHandler注解即可,比如新增一个汇付支付方式,那么新建一个Huifu.java即可。

实现类3-汇付:Huifu.java

@Service
@PayTypeHandler("HUIFU")
public class Huifu implements PaymentService{

    @Override
    public PaymentRes doPay(PaymentReq paymentReq){
        PaymentRes paymentRes = new PaymentRes();
        try {
            //入参校验
            validateParam(paymentReq);

            //具体支付逻辑
            paymentRes = specificPay(paymentReq);

        } catch (Exception e) {
            log.error("Payment system error:", e);
        }

        return paymentRes;
    }

 

总结

可能有些小伙伴会说,if-else它难道不香吗,为什么要浪费这么多时间把问题复杂化?if-else有它的优点,那就是简单,所有人都能看懂;设计模式则是能让代码更加灵活,可扩展性好。但是当系统越来越复杂,就不能只停留在代码的易读性或可扩展性中的某一点上,很多时候需要在多种性能上做出整体的权衡。用if-else也好,用设计模式也好,没有最好,只有适合,权衡利弊,按照业务需求和系统的实际情况作出自己的判断即可。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值