記一次重構,策略模式替換if else

弓長日含發表於2020-09-25

場景

先說一下大概要做的事情。我需要先從資料庫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,策略模式瞭解一下》,大概是作者刪除或者設為私密,我留下的連結已經失效,就不在此處分享。還是非常感謝作者給了案例作為參考。再者也感謝專案經理的提點,才有了新的收穫。

  • 最後,程式碼的重構優化也是從第一步先實現,再到抽象,封裝一步步的來的。沒有最優,只有更優。逐漸讓程式碼越來越靈活。

相關文章