由於業務中經常有需要判斷的if--eles操作,層層嵌套,看起來程序的可讀性太差,結合策略模式進行改造
方法一、一般有策略模式 + 工廠模式進行代碼的優化,減少 if---else;
方法二、還有就是利用策略模式 + SpringBoot提供的某些類 進行包裝
本次介紹采用方法二的方式,大概的思路是:
1、策略模式:將所有同類型的操作抽象出來一個接口(這個接口包含一個動作方法) 和 一個實現了接口的抽象類(不實現方法);
2、根據需求,將同類型的操作抽象成一個一個產品,繼承第一步的抽象類,並實現抽象方法,編寫自己的業務邏輯,注意此類需要注入spring容器;
3、自定義一個類級別注解,用來區分不同操作類型的標識,此自定義標識要有返回一個類型的屬性;
4、抽象出來一個處理所有產品的公共HandlerContext對象,此對象提供一個獲取具體產品類的方法,該方法有個入參用於表明是具體那個產品,同時該HandlerContext對象還具有Map類型的屬性變量,
存儲key為具體的類型,value為具體的產品類對象,該Map對象通過構造函數的方式注入初始化進來;
5、編寫一個加載所有產品類的全局process類,用於掃描加了注解@HandlerType的所有實現產品,給存儲key 和 value產品對象Map賦值,初始化HandlerContext 將其注冊到spring容器中;
需求
這里虛擬一個業務需求,讓大家容易理解。假設有一個訂單系統,里面的一個功能是根據訂單的不同類型作出不同的處理。
訂單實體:
service接口:
傳統實現
根據訂單類型寫一堆的if else:
下面采用方法二來進行優化:
1、策略模式:
將所有同類型的操作抽象出來一個接口(這個接口包含一個動作方法) 和 一個實現了接口的抽象類(不實現方法);
先定義一個數據傳輸的實體類DTO OrderDTO
1 @Data 2 public class OrderDTO { 3 4 private String code; 5 6 private BigDecimal price; 7 8 /** 9 * 訂單類型 10 * 1:普通訂單 11 * 2:團購訂單 12 * 3:促銷訂單 13 */ 14 private String orderType; 15 }
定義一個抽象類的接口:
IHandlerService
1 /** 2 * <p>Title: com.aier.cloud.biz.simplify</p> 3 * <p>Company:愛爾集團信息中心</p> 4 * <p>Copyright:Copyright(c)</p> 5 * User: duanm 6 * Date: 2019/10/31 15:33 7 * Description: No Description 8 */ 9 public interface IHandlerService { 10 11 String handler(OrderDTO orderDTO); 12 }
AbstractHandlerService:抽象類
1 public abstract class AbstractHandlerService implements IHandlerService { 2 3 abstract public String handler(OrderDTO orderDTO); 4 5 }
2、根據需求,將同類型的操作抽象成一個一個產品,繼承第一步的抽象類,並實現抽象方法,編寫自己的業務邏輯,注意此類需要注入spring容器;
團購訂單處理類:
1 @Component 2 @HandlerType(value = "2") 3 public class GroupHandler extends AbstractHandlerService { 4 5 @Override 6 public String handler(OrderDTO orderDTO) { 7 return "處理團購訂單"; 8 } 9 }
普通訂單處理類:
1 @Component 2 @HandlerType(value = "1") 3 public class NormalHandler extends AbstractHandlerService { 4 5 @Override 6 public String handler(OrderDTO orderDTO) { 7 return "處理普通訂單"; 8 } 9 }
促銷訂單處理類:
1 @Component 2 @HandlerType(value = "3") 3 public class PromotionHandler extends AbstractHandlerService { 4 @Override 5 public String handler(OrderDTO orderDTO) { 6 return "處理促銷訂單"; 7 } 8 }
注意事項:必須添加 @Component 注解,注入Spring容器 下面編寫自定義的注解實現 HandlerType
3、自定義一個類級別注解,用來區分不同操作類型的標識,此自定義標識要有返回一個類型的屬性;
1 @Target({ElementType.TYPE}) 2 @Retention(RetentionPolicy.RUNTIME) 3 @Documented 4 @Inherited 5 public @interface HandlerType { 6 String value(); 7 }
4、 抽象出來一個公共HandlerContext對象
抽象出來一個處理所有產品的公共HandlerContext對象,此對象提供一個獲取具體產品類的方法,該方法有個入參用於表明是具體那個產品,同時該HandlerContext對象還具有Map類型的屬性變量,
存儲key為具體的類型,value為具體的產品類對象,該Map類型變量初始化通過構造函數的方式注入;
1 public class HandlerContext { 2 3 private Map<String, Class> handlerMap; 4 5 public HandlerContext(Map<String, Class> handlerMap) { 6 this.handlerMap = handlerMap; 7 } 8 9 public AbstractHandlerService getInstance(String type) { 10 Class clazz = handlerMap.get(type); 11 if (clazz == null) { 12 throw new IllegalArgumentException("not found handler for type : " + type); 13 } 14 return (AbstractHandlerService) BeanTool.getBean(clazz); 15 } 16 }
5、編寫一個加載所有產品類的全局process類,用於掃描加了注解@HandlerType的所有實現產品,給存儲key 和 value產品對象Map賦值,初始化HandlerContext 將其注冊到spring容器中;
1 @Component 2 public class HandlerProcessor implements BeanFactoryPostProcessor { 3 4 private static final String HANDLER_PACKAGE = "com.aier.cloud.biz.simplify"; 5 6 /** 7 * 掃描@HandlerType,初始化HandlerContext 將其注冊到spring容器中 8 * 9 * @param configurableListableBeanFactory 10 * @throws BeansException 11 */ 12 @Override 13 public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { 14 Map<String, Class> handlerMap = Maps.newHashMap(); 15 16 ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); 17 provider.addIncludeFilter(new AnnotationTypeFilter(HandlerType.class)); 18 Set<BeanDefinition> candidateComponents = provider.findCandidateComponents(HANDLER_PACKAGE); 19 candidateComponents.forEach(Beanclass -> { 20 try { 21 Class<?> clazz = Class.forName(Beanclass.getBeanClassName()); 22 //獲取注解中的類型值 23 String type = clazz.getAnnotation(HandlerType.class).value(); 24 //將注解中的類型值做為key,對應的類作為value 保存在handlerMap中 25 handlerMap.put(type, clazz); 26 } catch (ClassNotFoundException e) { 27 e.printStackTrace(); 28 } 29 }); 30 31 //初始化HandlerContext類,將其注入到Spring容器中 32 HandlerContext handlerContext = new HandlerContext(handlerMap); 33 configurableListableBeanFactory.registerSingleton(HandlerContext.class.getName(), handlerContext); 34 35 } 36 }
注意:主要使用了spring的資料加載工具類,把所有的產品實現類都掃描 存儲到map中,並利用繼承 BeanFactoryPostProcessor 通過實現它的方法,動態的注入 HandlerContext 進入spring容器
自定義注解和抽象處理器都很簡單,那么如何將處理器注冊到spring容器中呢?
具體思路是:
1、掃描指定包中標有@HandlerType的類;
2、將注解中的類型值作為key,對應的類作為value,保存在Map中;
3、以上面的map作為構造函數參數,初始化HandlerContext,將其注冊到spring容器中;
幾個關鍵的工具類:
1 @Component 2 public class SpringContextUtils implements ApplicationContextAware { 3 4 private static ApplicationContext applicationContext; 5 6 @Override 7 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 8 SpringContextUtils.applicationContext = applicationContext; 9 } 10 11 /** 12 * 取得存儲在靜態變量中的ApplicationContext. 13 */ 14 public static ApplicationContext getApplicationContext() { 15 checkApplicationContext(); 16 return applicationContext; 17 } 18 19 /** 20 * 清除applicationContext靜態變量. 21 */ 22 public static void cleanApplicationContext() { 23 applicationContext = null; 24 } 25 26 private static void checkApplicationContext() { 27 if (applicationContext == null) { 28 throw new IllegalStateException("applicaitonContext未注入,請在applicationContext.xml中定義SpringContextHolder"); 29 } 30 } 31 32 /** 33 * 從靜態變量ApplicationContext中取得Bean, 自動轉型為所賦值對象的類型. 34 */ 35 @SuppressWarnings("unchecked") 36 public static <T> T getBean(String name) { 37 checkApplicationContext(); 38 return (T) applicationContext.getBean(name); 39 } 40 41 /** 42 * 從靜態變量ApplicationContext中取得Bean, 自動轉型為所賦值對象的類型. 43 */ 44 @SuppressWarnings("unchecked") 45 public static <T> T getBean(Class<T> clazz) { 46 checkApplicationContext(); 47 return (T) applicationContext.getBeansOfType(clazz); 48 } 49 }
1 public class BeanTool { 2 3 public static <T> T getBean(Class<T> clazz) { 4 String clazzName = clazz.getName(); 5 clazzName = clazzName.replace(".",","); 6 String[] split = clazzName.split(","); 7 String name = split[split.length - 1]; 8 return SpringContextUtils.getBean(lowerFirst(name)); 9 } 10 11 public static String lowerFirst(String oldStr) { 12 char[] chars = oldStr.toCharArray(); 13 chars[0] += 32; 14 return String.valueOf(chars); 15 } 16 }
注意此處可以優化下,可以簡單來獲取類的名字,
1 public static <T> T getBean(Class<T> clazz) { 2 //獲取類的名字 3 String simpleName = clazz.getSimpleName(); 4 //根據類的名稱獲取類的實列對象 5 return SpringContextUtils.getBean(lowerFirst(simpleName)); 6 }
思考: 還是JAVA反射 不太熟悉,導致走了彎路來處理,JAVA反射還是要吃透,多寫寫。
(1)反射機制極大的提高了程序的靈活性和擴展性,降低模塊的耦合性,提高自身的適應能力。
6、測試運行代碼,查看結果
測試運行:在service層進行調用,可以如下來編寫:
1 @Service 2 public class OrderServiceImpl implements IOrderService { 3 4 @Resource 5 private HandlerContext handlerContext; 6 7 @Override 8 public String handle(OrderDTO orderDTO) { 9 IHandlerService instance = handlerContext.getInstance(orderDTO.getOrderType()); 10 System.out.println(instance.handler(orderDTO)); 11 return instance.handler(orderDTO); 12 } 13 }
運行結果如下:
最后請注意一點,HandlerProcessor和BeanTool必須能被掃描到,或者通過@Bean的方式顯式的注冊,才能在項目啟動時發揮作用。
總結
利用策略模式可以簡化繁雜的if else代碼,方便維護,而利用自定義注解和自注冊的方式,可以方便應對需求的變更。本文只是提供一個大致的思路,還有很多細節可以靈活變化,例如使用枚舉類型、或者靜態常量,作為訂單的類型,相信你能想到更多更好的方法。
2、后續追加更優雅處理
針對實現了策略模式的具體操作類,在業務處理類中,可以通過spring的已有功能進行處理,怎么通過不同的類型,獲取到該類型的實現類。
通過業務類的構造方法,
hashMap hp = new hashMap();// 最好采用線程安全的MAP對象,需要優化 @Autowired public AemrMessageServiceImpl(List<策略模式的接口對象參數>){ // 初始化map,存儲起來 map.put(業務類型key,業務類型實現) }
思路:
1、通過spring的IOC快速實現通過類型type注入這個type的所有實現;
2、通過申明一個線程安全的Map初始化數據,在業務類的構造函數中初始化進來,map.put(業務類型,策略模式業務的具體實現類);
3、在具體使用的業務類處理方法中,通過類型獲從map中拿到具體的實現類型,完成調用;