记一次重构,策略模式替换if else

场景

先说一下大概要做的事情。我需要先从数据库A中筛选出一周的基础数据,调用接口获取数据库B中的规则,再做一次数据筛选,然后入库数据库B。定时任务每周跑一次。
呐,说到这里,也大概知道了,在数据筛选那里,将会有业务判断。伪代码就集中展示那里。

第1版

考虑时间等各种因素,先实现再说。此处并没有用到数据库B中的规则,直接在项目中写的固定值。

for(CustomerResource customerResource : customerResourceList){
	//TODO 
	//V1
	//条件1 
	if(customerResource.getFactoryPrice().intValue() < -10){
	    condition1 = true;
	}else {
	    continue;//条件1未满足,直接忽略
	}
	//条件2 
	if(customerResource.getLastWeekSales().intValue() > 100){
	    condition2 = true;
	}else {
	    continue;//条件2未满足,直接忽略
	}
	//条件3 
	if(factoryDay.get(customerResource.getFactoryCode()) == null ? false : factoryDay.get(customerResource.getFactoryCode()) > 3){
	    condition3 = true;
	}else{condition3 = false;}
	//条件4 
	if(factorySales.get(customerResource.getFactoryCode()) == null ? false :
	        customerResource.getLastWeekSales().divide(factorySales.get(customerResource.getFactoryCode()),2,BigDecimal.ROUND_HALF_UP).doubleValue() > 1.1){
	    condition4 = true;
	}else{condition4 = false;}
	
	//根据4个条件归类
}

第2版

等到第1版本实现以后,筛选出的数据基本上已经是想要的数据。但是不利于规则的扩展。便于维护,后续在后管系统做页面。

存在每个工厂的规则数量(目前4条)是一样,但是规则中比较的数值存在变动,比较存在变动(符号)。

如此一来,第1版本显然已经不符合需求了。

for(CustomerResource customerResource : customerResourceList){
	//获取工厂的规则
    List<CustomerResourceRule> rules = factoryRulesMap.get(customerResource.getFactoryCode());
    if(rules == null){//未获取到该工厂的规则
        continue;
    }
    for(CustomerResourceRule rule : rules){
		// V2
		//条件1 
		if(ConditionEnum.CONDITION1.getRuleCode().equals(rule.getRuleCode())){
		    switch (SymbolEnum.getBySymbol(rule.getSymbol())){
		        case GT:
		            condition1 = customerResource.getFactoryPrice().intValue() > Integer.parseInt(rule.getSetValue());
		            break;
		        case LT:
		            condition1 = customerResource.getFactoryPrice().intValue() < Integer.parseInt(rule.getSetValue());
		            break;
		        default:break;
		    }
		}
		//条件2 
		if(ConditionEnum.CONDITION2.getRuleCode().equals(rule.getRuleCode())){
		    switch (SymbolEnum.getBySymbol(rule.getSymbol())){
		        case GT:
		            condition2 = customerResource.getLastWeekSales().intValue() > Integer.parseInt(rule.getSetValue());
		            break;
		        case LT:
		            condition2 = customerResource.getLastWeekSales().intValue() > Integer.parseInt(rule.getSetValue());
		            break;
		        default:break;
		    }
		}
		//条件3 
		if(ConditionEnum.CONDITION3.getRuleCode().equals(rule.getRuleCode())){
		    List<FactoryClinkerRatio> ratios = factoryRatioMap.get(customerResource.getFactoryCode());
		    if(ratios != null){        
		        switch (SymbolEnum.getBySymbol(rule.getSymbol())){
		            case GT:
		                List<FactoryClinkerRatio> ratioList = ratios.stream().filter(s -> s.getRatios().doubleValue() > Double.parseDouble(rule.getSetValue())).collect(Collectors.toList());
		                condition3 = ratioList.size() > 3;
		                break;
		            case LT:
		                List<FactoryClinkerRatio> ratioList1 = ratios.stream().filter(s -> s.getRatios().doubleValue() < Double.parseDouble(rule.getSetValue())).collect(Collectors.toList());
		                condition3 = ratioList1.size() > 3;
		                break;
		            default:break;
		        }
		    }
		}
		//条件4 
		if(ConditionEnum.CONDITION4.getRuleCode().equals(rule.getRuleCode())){
		    BigDecimal salePlan = factorySalesMap.get(customerResource.getFactoryCode());
		    if(salePlan != null){
		        switch (SymbolEnum.getBySymbol(rule.getSymbol())){
		            case GT:
		                condition4 = customerResource.getLastWeekSales().divide(salePlan,2,BigDecimal.ROUND_HALF_UP).doubleValue() > Double.parseDouble(rule.getSetValue());
		                break;
		            case LT:
		                condition4 = customerResource.getLastWeekSales().divide(salePlan,2,BigDecimal.ROUND_HALF_UP).doubleValue() < Double.parseDouble(rule.getSetValue());
		                break;
		            default:break;
		        }
		    }
		}

		//根据4个条件归类
	}
}

第3版

第二版的代码已经基本实现了功能。那时项目经理告诉我,还有更好的写法,去了解一下策略模式。

就开始了策略模式的学习之路,边学习边实践。(关于设计模式,后续有空会在设计模式专栏中更新,此处就先不介绍了)。
下图为本次项目中的策略类图:
在这里插入图片描述

重构步骤
  1. 辅助类

    1. 枚举

      /**
       * 条件枚举
       * Created by zhanghan_a on 2020/6/10.
       */
      public enum ConditionEnum {
          CONDITION1("1"),
          CONDITION2("2"),
          CONDITION3("3"),
          CONDITION4("4");
      
          private String ruleCode;
      
          ConditionEnum(String ruleCode){
              this.ruleCode = ruleCode;
          }
      
          public String getRuleCode(){
              return ruleCode;
          }
      
          public static ConditionEnum getConditionByCode(String ruleCode){
              for(ConditionEnum conditionEnum : values()){
                  if(conditionEnum.getRuleCode().equals(ruleCode)){
                      return conditionEnum;
                  }
              }
              return null;
          }
      }
      
      /**
       * 符号枚举
       * Created by zhanghan_a on 2020/6/10.
       */
      public enum SymbolEnum {
          GT(">"),
          LT("<"),
          WRONG("wrong");
      
          private String symbol;
      
          SymbolEnum(String symbol){
              this.symbol = symbol;
          }
      
          public String getSymbol(){
              return this.symbol;
          }
      
          public static SymbolEnum getBySymbol(String symbol){
              for(SymbolEnum symbolEnum : values()){
                  if(symbolEnum.getSymbol().equals(symbol)){
                      return symbolEnum;
                  }
              }
              return WRONG;//返回错误的比较符号
          }
      }
      
    2. 自定义注解

      /**
       * Created by zhanghan_a on 2020/6/12.
       */
      @Target({ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      public @interface ConditionTypeAnnotation {
          ConditionEnum type();
      }
      
  2. 在项目中新建策略包:strategy

    1. 策略类接口

      /**
       * Created by zhanghan_a on 2020/6/12.
       */
      public interface ConditionStrategy {
          String check(ConditionParams conditionParams);
      }
      
    2. 策略实现类

      @Service
      @ConditionTypeAnnotation(type = ConditionEnum.CONDITION1)
      public class FirstCondition implements ConditionStrategy{
          @Override
          public String check(ConditionParams conditionParams) {
              //判断
              boolean condition = false;
              //TODO 条件1的判断
              return condition ? ConditionEnum.CONDITION1.getRuleCode() : "";
          }
      }
      
      @Service
      @ConditionTypeAnnotation(type = ConditionEnum.CONDITION2)
      public class SecondCondition implements ConditionStrategy {
          @Override
          public String check(ConditionParams conditionParams) {
              //判断
              boolean condition = false;
              //TODO 条件2的判断
              return condition ? ConditionEnum.CONDITION2.getRuleCode() : "";
          }
      }
      
      @Service
      @ConditionTypeAnnotation(type = ConditionEnum.CONDITION3)
      public class ThirdCondition implements ConditionStrategy {
          @Override
          public String check(ConditionParams conditionParams) {
              //判断
              boolean condition = false;
              //TODO 条件3的判断
              return condition ? ConditionEnum.CONDITION3.getRuleCode() : "";
          }
      }
      
      @Service
      @ConditionTypeAnnotation(type = ConditionEnum.CONDITION4)
      public class ForthCondition implements ConditionStrategy {
          @Override
          public String check(ConditionParams conditionParams) {
              //判断
              boolean condition = false;
              //TODO 条件4的判断
              return condition ? ConditionEnum.CONDITION4.getRuleCode() : "";
          }
      }
      
  3. 在config文件夹下或util文件夹下新增类

    1. 新增StrategyContext类

      /**
       * Created by zhanghan_a on 2020/6/12.
       */
      public class StrategyContext {
          private Map<ConditionEnum,Class> strategyMap;
      
          public StrategyContext(Map<ConditionEnum, Class> strategyMap) {
              this.strategyMap = strategyMap;
          }
      
          public ConditionStrategy getStrategy(ConditionEnum conditionEnum){
              if(conditionEnum == null){
                  return null;
              }
              if(strategyMap == null){
                  return null;
              }
      
              Class clazz = strategyMap.get(conditionEnum);
              if(clazz == null){
                  return null;
              }
              return (ConditionStrategy)BeanTools.getBean(clazz);
          }
      }
      
    2. 新增StrategyProcessor类

      此处用到了反射工具包。请在pom.xml中添加依赖

      <dependency>
          <groupId>org.reflections</groupId>
          <artifactId>reflections</artifactId>
          <version>0.9.10</version>
      </dependency>
      

      注意添加@Component 注解

      /**
       * Created by zhanghan_a on 2020/6/12.
       */
      @Component
      public class StrategyProcessor implements BeanFactoryPostProcessor {
      
          private static final String BASE_PACKAGE = "com.**.**.strategy";//此处填写对应项目的包名即可
      
          @Override
          public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
              Map<ConditionEnum,Class> strategyMap = new HashMap<>();
              Reflections reflections = new Reflections(BASE_PACKAGE);//扫描策略包
              reflections.getTypesAnnotatedWith(ConditionTypeAnnotation.class).forEach(clazz -> {//寻找ConditionTypeAnnotation注解的类
                  ConditionEnum conditionEnum = clazz.getAnnotation(ConditionTypeAnnotation.class).type();//获取对应的条件
                  strategyMap.put(conditionEnum,clazz);//映射条件对应的策略实现类
              });
              StrategyContext strategyContext = new StrategyContext(strategyMap);
              configurableListableBeanFactory.registerSingleton(StrategyContext.class.getName(),strategyContext);//注册为单例
          }
      }
      
  4. 在之前的业务实现类中添加strategyContext

    @Autowired
    private StrategyContext strategyContext;
    
  5. 替换掉第二版的代码

    for(CustomerResource customerResource : customerResourceList){
    	//获取工厂的规则
        List<CustomerResourceRule> rules = factoryRulesMap.get(customerResource.getFactoryCode());
        if(rules == null){//未获取到该工厂的规则
            continue;
        }
        for(CustomerResourceRule rule : rules){
    		// V3
    		ConditionEnum conditionEnum = ConditionEnum.getConditionByCode(rule.getRuleCode());//根据规则代码获取对应的枚举类型
    		if(conditionEnum == null){
                continue;
            }
            conditionParams.setRule(rule);//设置条件参数
            ConditionStrategy conditionStrategy = strategyContext.getStrategy(conditionEnum);//根据枚举类型获取对应的实现类
            resultList.add(conditionStrategy.check(conditionParams));//每次筛选的条件记录在resultList
    		//根据4个条件归类
    	}
    }
    

这样一来,简洁了很多,也便于后续的扩展。

  • 小编工作两年有余,知识的广度与深度均不够,尚在学习中。如果您在阅读中发现各种错误,还望指出及时纠正。

  • 本篇只是提供一个思路,毕竟每个项目中的业务场景不同。有了这个思路就足够了,剩下的只需要你去找几篇类似的博客,结合自己的需求,大胆尝试。

  • 当初参考来自于简书的一篇文章《还在业务中用if else,策略模式了解一下》,大概是作者删除或者设为私密,我留下的链接已经失效,就不在此处分享。还是非常感谢作者给了案例作为参考。再者也感谢项目经理的提点,才有了新的收获。

  • 最后,代码的重构优化也是从第一步先实现,再到抽象,封装一步步的来的。没有最优,只有更优。逐渐让代码越来越灵活。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页