• 设计模式——策略模式实战


    策略模式——支付代码优化

    六大设计原则

    单一职责原则

    一个类只负责一个功能领域中的相应职责。高内聚、低耦合。

    理解:不同的类具备不同的职责,各司其职。做系统设计是,如果发现有一个类拥有了两种职责,那么就要问一个问题:可以将这个类分成两个类吗?如果真的有必要,那就分开,千万不要让一个类干的事情太多。

    开闭原则

    对拓展开放,对修改关闭。不修改原有代码的情况下进行拓展。

    理解:类、模块、函数,可以去扩展,但不要去修改。如果要修改代码,尽量用继承或组合的方式来扩展类的功能,而不是直接修改类的代码。当然,如果能保证对整个架构不会产生任何影响,那就没必要搞的那么复杂,直接改这个类吧。

    里氏代替原则

    所有引用基类(父类)的地方必须能透明的使用其子类的对象。

    理解:父类可被子类替换,但反之不一定成立。也就是说,代码中可以将父类全部替换为子类,程序不会出现异常,但反过来就不一定了。

    依赖倒置原则

    抽象不应该依赖于细节,细节应当依赖于抽象。

    理解:高层模块不应该依赖于底层模块,而应该依赖于抽象。抽象不应依赖于细节,细节应依赖于抽象。应该面向接口编程,不该面向实现类编程。面向实现类编程相当于就事论事,那是正向依赖;面向接口编程,相当于透过现象看本质,抓住事务的共性,那就是反向依赖,即依赖倒置。

    接口隔离原则

    接口拆分。当一个接口太大时,我们需要将它分割成一些更细小的接口。

    理解:不要对外暴露没有实际意义的接口。也就是说,尽量保证接口的实用性。当需要对外暴露接口时,需要再三斟酌,若没必要对外提供就删了吧,因为一旦提供了就意味着,将来要多做一件事情,何苦给自己找事做呢。

    迪米特法则

    理解:减少依赖。一个软件实体应当尽可能少地与其他实体发生相互作用。

    例子1:

    /**
        * 打折接口
        */
      public double sale(String userType,double fee){
          if("normal".equals(userType)){
               
              return fee*1.2;
          }else if("vip".equals(userType)){
              return fee*0.8;
          }else if("svip".equals(userType)){
              return fee*0.6;
          }
          return fee;
      }

    改进后代码:把每个折扣的计算方法分开,新增normalCalculate和sVipCalculate计算类

        @Autowired
      NormalCalculate normalCalculate;
      @Autowired
      VipCalculate vipCalculate;
      @Autowired
      SVipCalculate sVipCalculate;

      public double sale(String userType,double fee){
          //单一职责
          if("normal".equals(userType)){
              return normalCalculate.discount(fee);
          }else if("vip".equals(userType)){
              return vipCalculate.discount(fee);
          }else if("svip".equals(userType)){
              return sVipCalculate.discount(fee);
          }
          return fee;
      }
    @Service
    public class VipCalculate {
      public double discount(double fee) {
          return fee*0.8;
      }
    }

    @Service
    public class NormalCalculate {

      public double discount(double fee) {
          BigDecimal xs=new BigDecimal(String.valueOf(1.1));
          return new BigDecimal(Double.toString(fee)).multiply(xs).doubleValue();
      }
    }

    继续改进:新增计算接口ICalculate,NormalCalculate和VipCalculate进行修改

    public interface ICalculate {
      public String userType();

      public double discount(double fee);
    }

    /**
    * @Author: yechongbai
    * @Date: 2020/4/23 0:51
    * @Copyright: www.zektech.cn
    * @since 1.0
    */
    @Service
    public class NormalCalculate implements ICalculate {
      @Override
      public String userType() {
          return "normal";
      }

      @Override
      public double discount(double fee) {
          BigDecimal xs=new BigDecimal(String.valueOf(1.1));
          return new BigDecimal(Double.toString(fee)).multiply(xs).doubleValue();
      }
    }


    /**
    * @Author: yechongbai
    * @Date: 2020/4/23 0:51
    * @Copyright: www.zektech.cn
    * @since 1.0
    */
    @Service
    public class VipCalculate implements ICalculate {
      @Override
      public String userType() {
          return "vip";
      }

      @Override
      public double discount(double fee) {
          return fee*0.8;
      }
    }

    /**
    * @Author: yechongbai
    * @Date: 2020/4/23 0:51
    * @Copyright: www.zektech.cn
    * @since 1.0
    */
    @Service
    public class SVipCalculate implements ICalculate {
      @Override
      public String userType() {
          return "svip";
      }

      @Override
      public double discount(double fee) {
          return fee*0.6;
      }
    }

    然后SaleService类进行调整

    //icalculateList 改用一个map存起来
      HashMap<String,ICalculate> calculateHashMap=new HashMap<>();

      //1.改注入的方式 spring特殊功能 --注入list,map
      @Autowired
      public SaleService(List<ICalculate> iCalculateList){
          for (ICalculate iCalculate:iCalculateList){
              calculateHashMap.put(iCalculate.userType(),iCalculate);
          }

      }

      public double sale(String userType,double fee){
          //TODO 根据不同用户类型,选用不同的对象,调用同样的接口
          //本质来说就是一个 获取对象的逻辑
          ICalculate iCalculate=calculateHashMap.get(userType);
          if(iCalculate==null){
              return fee;
          }
          return iCalculate.discount(fee);
      }

     

     

    例子2:原支付逻辑

    /**
        * 微信公众号/小程序预下单
        * @param businessId 业务id-发起支付主键id,用来关联业务用
        * @param userId 用户id/学生id
        * @param spbillIp 微信支付手机ip
        * @param schoolId 学校id
        */
      public String unifiedorderJH(String businessId ,String userId,String spbillIp,String schoolId) throws Exception {
          if(StringUtils.isBlank(businessId)){
              return buildResult(SystemStatusEnum.ReqParamNull.getValue(),"业务id不能为空");
          }
          if(StringUtils.isBlank(userId)){
              return buildResult(SystemStatusEnum.ReqParamNull.getValue(),"角色id不能为空");
          }
          if(StringUtils.isBlank(schoolId)){
              return buildResult(SystemStatusEnum.ReqParamNull.getValue(),"学校id不能为空");
          }
          //获取学校配置
          SysSchoolConfigVO configVO = sysSchoolConfigService.getInfoById(schoolId);
          if(StringUtils.isBlank(configVO.getMiniAppid1())){
              return buildResult(SystemStatusEnum.ReqParamNull.getValue(),"小程序id为空,请查看学校是否已经配置信息");
          }
          String mchId=sysConfigService.getConfigValue(schoolId, SysConfigConfig.GF_MCHID);
          String instId=sysConfigService.getConfigValue(schoolId, SysConfigConfig.GF_INSTID);
          String publicKey=sysConfigService.getConfigValue(schoolId, SysConfigConfig.GF_PUK);
          String privateKey=sysConfigService.getConfigValue(schoolId, SysConfigConfig.GF_PVK);
          String judgeRes=payParamJudge(configVO,mchId,instId,publicKey,privateKey);
          if (judgeRes!=null){
              return judgeRes;
          }
          SysWxUserVO wxUser=new SysWxUserVO();
          wxUser.setUserId(userId);
          SysWxUser wxUserObj =sysWxUserService.getWxUser(wxUser);
          if(wxUserObj==null||StringUtils.isBlank(wxUserObj.getOpenId1())){
              return buildResult(SystemStatusEnum.NoData.getValue(),"用户不存在");
          }
          SmNoticePaymoneyVO noticePaymoney=new SmNoticePaymoneyVO();
          noticePaymoney.setStId(userId);
          noticePaymoney.setNoticeId(businessId);
          noticePaymoney=smNoticePaymoneyService.findSmNoticePaymoneyByStId(noticePaymoney);
          if(noticePaymoney==null){
              return buildResult(SystemStatusEnum.NoData.getValue(),"订单不存在");
          }
          if(noticePaymoney.getCost()==null){
              return buildResult(SystemStatusEnum.NoData.getValue(),"支付金额为空");
          }
          if(SmNoticePaymoneyEnum.DELIVERED.getStatus().equals(noticePaymoney.getStatus())){
              return buildResult(SystemStatusEnum.InvalidParams.getValue(),"该订单已缴费或作废,无法继续缴费");
          }
          SmNoticeVO notice=new SmNoticeVO();
          notice.setNoticeId(businessId);
          SmNoticeVO noticeObj = smNoticeService.getOneData(notice);
          if (noticeObj==null) {
              return buildResult(SystemStatusEnum.NoData.getValue(),"业务数据不存在");
          }
          String subject=noticeObj.getSubject();
          //1.校验数据
          if(noticePaymoney.getCost()==null){
              return buildResult(SystemStatusEnum.ReqParamNull.getValue(),"费用不能为空");
          }
          if(StringUtils.isBlank(wxUserObj.getOpenId1())){
              return buildResult(SystemStatusEnum.ReqParamNull.getValue(),"用户openid不能为空");
          }
          if(StringUtils.isBlank(subject)){
              return buildResult(SystemStatusEnum.ReqParamNull.getValue(),"费用名称不能为空");
          }
          //元转分-判断金额是否小于等于0
          if(noticePaymoney.getCost().compareTo(BigDecimal.ZERO)<=0)
          {
              return buildResult(SystemStatusEnum.ReqParamNull.getValue(),"订单金额必须大于0");
          }
          StBaseInfoVO stObj=stBaseInfoService.getByStName(userId);
          //判断支付方式
          switch (configVO.getPayWay()){
              //聚合支付
              case 0:
                  return payService.unifiedorderJH(configVO.getMiniAppid1(),mchId,businessId,BusinessTypeEnum.CLASS_ACTIVITY,userId,noticePaymoney.getCost().toString(),
                          wxUserObj.getOpenId1(),spbillIp,subject,schoolId,stObj.getClassName(),stObj.getName());
              //富友支付
              case 1:
                  return fyPayService.wxPreCreate(instId,mchId,configVO.getMiniAppid1(),businessId,BusinessTypeEnum.CLASS_ACTIVITY,userId,noticePaymoney.getCost().toString(),
                          wxUserObj.getOpenId1(),spbillIp,subject,schoolId,"01_WXPAY_PBPM",publicKey,privateKey,stObj.getClassName(),stObj.getName());
                  default:
                      return buildResult(SystemStatusEnum.InvalidParams.getValue(),"请检查学校是否配置了支付方式");
          }
      }

    主要问题在判断支付方式上面,如果继续对接相应的支付,是否是要继续在相应的下单,退款,查询等接口都增加判断,会让这个代码越来越难维护。。。

    //判断支付方式
          switch (configVO.getPayWay()){
              //聚合支付
              case 0:
                  return payService.unifiedorderJH(configVO.getMiniAppid1(),mchId,businessId,BusinessTypeEnum.CLASS_ACTIVITY,userId,noticePaymoney.getCost().toString(),
                          wxUserObj.getOpenId1(),spbillIp,subject,schoolId,stObj.getClassName(),stObj.getName());
              //富友支付
              case 1:
                  return fyPayService.wxPreCreate(instId,mchId,configVO.getMiniAppid1(),businessId,BusinessTypeEnum.CLASS_ACTIVITY,userId,noticePaymoney.getCost().toString(),
                          wxUserObj.getOpenId1(),spbillIp,subject,schoolId,"01_WXPAY_PBPM",publicKey,privateKey,stObj.getClassName(),stObj.getName());
                  default:
                      return buildResult(SystemStatusEnum.InvalidParams.getValue(),"请检查学校是否配置了支付方式");
          }

    违反代码六大设计原则之一:开闭原则,如何进行优化修改?

    利用设计模式之一:策略模式进行修改

    定义一个支付的接口,里面包含:下单,退款,交易查询,退款查询等无论接入哪个支付通道都要用到的方法。

    步骤1:新增支付接口IPayServiceImp

    public interface IPayServiceImp {

      //支付类型
      public String payType();

      /**
        * 下单
        * @return
        */
      public String unifiedorderJH(String businessId ,String userId,String spbillIp,String schoolId);

      /**
        * 退款
        * @return
        */
      public String refundJH(String userId,String tradeNo,String schoolId,String refundFee,String remark);

      /**
        * 交易查询
        * @return
        */
      public String findOrderJH(String tradeNo,String schoolId);

      /**
        * 退款查询
        * @return
        */
      public String findRefOrderJH(String tradeNo,String refundNo,String schoolId);

    }

    步骤2:分别新增富友支付和聚合支付实现类:FuyouPayService和JuhePayService

    @Service
    public class FuyouPayService implements IPayServiceImp{
      @Override
      public String payType() {
          return "fuyou";
      }

      @Override
      public String unifiedorderJH(String businessId, String userId, String spbillIp, String schoolId) {
          //TODO 编写逻辑
          return "成功调用富有支付下单方法";
      }

      @Override
      public String refundJH(String userId, String tradeNo, String schoolId, String refundFee, String remark) {
          return "成功调用富有支付退款方法";
      }

      @Override
      public String findOrderJH(String tradeNo, String schoolId) {
          return null;
      }

      @Override
      public String findRefOrderJH(String tradeNo, String refundNo, String schoolId) {
          return null;
      }
    }
    @Service
    public class JuhePayService implements IPayServiceImp {
      @Override
      public String payType() {
          return "juhe";
      }

      @Override
      public String unifiedorderJH(String businessId, String userId, String spbillIp, String schoolId) {
          //TODO 编写逻辑
          return "成功调用聚合支付下单方法";
      }

      @Override
      public String refundJH(String userId, String tradeNo, String schoolId, String refundFee, String remark) {
          return "成功调用聚合支付退款方法";
      }

      @Override
      public String findOrderJH(String tradeNo, String schoolId) {
          return null;
      }

      @Override
      public String findRefOrderJH(String tradeNo, String refundNo, String schoolId) {
          return null;
      }
    }

    步骤3:实现支付调用,创建一个支付业务类ArcPayService,根据传输的支付类型自动匹配相应的支付接口,该方法利用了设计模式之一:策略模式。具体原理:利用spring的特性,在类初始化的时候,注入所有的支付list,然后以payType为key,把这个list存储在map中,每次接口调用,根据payType这个key,找到相应的支付实现类,完成支付功能的正确调用。

    @Service
    public class ArcPayService {

      /**
        * iPayServiceImpList用一个map存起来
      */
      HashMap<String,IPayServiceImp> iPayMap=new HashMap<>();

      /**
        * 初始化,注入所有的支付List
        */
      @Autowired
      public ArcPayService(List<IPayServiceImp> iPayServiceImpList){
          for (IPayServiceImp iPayServiceImp:iPayServiceImpList){
              iPayMap.put(iPayServiceImp.payType(),iPayServiceImp);
          }
      }

      /**
        * 下单
        * @param payType
        * @return
        */
      public String unifiedorderJH(String payType){
          IPayServiceImp iPayServiceImp=iPayMap.get(payType);
          if(iPayServiceImp==null){
              return "支付方式不存在";
          }
          return iPayServiceImp.unifiedorderJH("","","","");
      }

      /**
        * 退款
        * @param payType
        * @return
        */
      public String refundJH(String payType){
          IPayServiceImp iPayServiceImp=iPayMap.get(payType);
          if(iPayServiceImp==null){
              return "支付方式不存在";
          }
          return iPayServiceImp.refundJH("","","","","");
      }
       
    }

    步骤4:测试,新增测试类ArcPayServiceTest

    @SpringBootTest
    @RunWith(SpringRunner.class)
    public class ArcPayServiceTest {

      @Autowired
      ArcPayService arcPayService;

      @Test
      public void unifiedorderJH() {
          String fuyou= arcPayService.unifiedorderJH("fuyou");
          System.out.println("富有:"+fuyou);
          String juhe= arcPayService.unifiedorderJH("juhe");
          System.out.println("聚合:"+juhe);
          String xx= arcPayService.unifiedorderJH("xxx");
          System.out.println("其他:"+xx);

      }

      @Test
      public void refundJH() {
      }
    }

    输出相应的结果如下:

    富有:成功调用富有支付下单方法
    聚合:成功调用聚合支付下单方法
    其他:支付方式不存在

    至此完成了这个代码的升级改造,后续继续增加支付对接,只要继续增加一个支付方式的实现类就可以了,完全不用修改原来的代码。

  • 相关阅读:
    成为 Team Leader 后我最关心的那些事
    《管理的实践》读书心得
    玩黑客学校CTF
    DHCP中继器
    test
    初窥XSS跨站脚本攻击
    TCP/IP模型
    逻辑漏洞-客户端验证的邮箱-Web渗透实例之中国教育部青少年普法网站逻辑漏洞
    逻辑漏洞-支付风险-大疆某处支付逻辑漏洞可1元买无人机
    逻辑漏洞-密码找回之验证码发给了客户端
  • 原文地址:https://www.cnblogs.com/shuideqing/p/12759771.html
Copyright © 2020-2023  润新知