導讀:我們在實際項目開發的過程中常常會因處理多狀態的情況導致代碼中產生了大量的if-else,過多的判斷語句及嵌套會導致一個方法變得很冗長,而且當新增狀態時需要改動代碼邏輯也是違背了設計模式中開放-封閉原則。本文將通過一個優化的Demo,
主要運用狀態設計模式優化if-else語句並使其遵循開放-封閉原則。其中還會運用到包括:IOC容器、BeanFactoryPostProcessor後置處理器、自定義註解、ResourceLoaderAware資源加載、反射等知識概念。通過本案例希望能給朋友們帶來一些代碼優化的思路和啟發,如果覺得本例有不足之處或者有更好的優化建議歡迎提出,共同討論與進步。場景
後臺系統提供一個通用處理審核結果的方法,該接口根據審核類型和審核結果進行相應的後續邏輯處理。類圖如下:
CheckResultHandleManager是一個審核結果處理類,其提供了一個deal() 方法用於根據審核類型和審核結果進行相應的後續邏輯處理。
審核類型枚舉(CheckTypeEnum)
- 賬戶開戶(ACCOUNT_OPEN)
- 賬戶禁用(ACCOUNT_DISABLE)
- 賬戶啟用(ACCOUNT_ENABLE)
- 賬戶充值(ACCOUNT_RECHARGE)
- 賬戶提款(ACCOUNT_DRAWING)
- 賬戶綁卡(ACCOUNT_BINDCARD)
審核結果枚舉(CheckReusltEnum)
- 審核通過(SUCCESS)
- 審核不通過(FAIL)
由於不同的審核類型後續處理方式不同,如賬戶開戶要做的後續處理為創建一條賬號記錄,而賬戶充值則是變動對應賬戶的餘額及新增明細等。因為審核類型很多處理情況又不同,所以會導致代碼產生很多的if -else判斷,如下所示:
<code>/*** 審核結果處理類*/@Componentpublic class CheckResultHandleManager { public void deal(CheckResultReq req) { //審核類型為--賬戶開戶 if (CheckTypeEnum.ACCOUNT_OPEN.getCode().equals(req.getCheckType())){ if (CheckResultEnum.SUCCESS.getCode().equals(req.getResult())){ //新增賬戶 } else { log.info("審核不通過"); } } //審核類型為-賬戶充值 else if (CheckTypeEnum.ACCOUNT_RECHARGE.getCode().equals(req.getCheckType())){ if (CheckResultEnum.SUCCESS.getCode().equals(req.getResult())){ //增加賬戶餘額 //新增交易明細 } else { log.info("審核不通過"); } } ......此處省略其他的 if-else}/<code>
引入狀態設計模式
上面deal()方法中之所以出現很多的判斷分支,其本質原是因為“狀態”太多。我們可以對其進行優化,優化的方案可選用設計模式中的狀態設計模式,基礎的狀態設計模式類圖如下:
簡單描述:抽取一個"狀態"抽象類並提供一個抽象handle()方法,接著把"狀態"枚舉改為多個繼承抽象類的“狀態”實現類,在各個實現類中進行各自的邏輯判斷和處理。這麼做雖然會導致增加了很多的類,但是優勢在於其遵循了開放-封閉原則,當有新“狀態”加入時只需要新增類,而不用去變動到deal()方法中的邏輯。
基於狀態設計模式對代碼進行優化的思路
在應用狀態設計模式的同時,我們再結合自定義註解、掃描器、反射等技術使代碼變得更加靈活。即將新增以下這些類:
優化大致思路如下:
- 應用狀態設計模式,創建一個 審理類型 抽象類,抽象類提供一個handle()方法。將每一個審核類型都創建為一個實現類,繼承審理類型抽象類
- 自定義一個HandlerType註解,每一個 審核類型 實現類都加上該註解,註解值為 審核類型 對應的枚舉值
- 創建一個HandlerContext處理上下文,其作用:1、提供一個HashMap用於存放信息 2、提供一個getInstance()獲取實例Bean的方法
- 創建一個Processer類實現BeanFactoryPostProcessor用於做預處理(1、掃描獲取符合條件的Class信息並存入HashMap 2、創建HandlerContext並交由Spring管理)
- 最後修改CheckResultHandleManager的deal()方法,化繁為簡
實際操作
下面將劃分為四步進行代碼優化
- 狀態設計模式的應用 + 標記上自定義註解
- 創建一個處理上下文
- 實現BeanFactoryPostProcessor的後置處理器進行預處理
- 優化CheckResultHandleManager的deal()方法中的邏輯
一、狀態設計模式的應用 + 自定義註解
1、建立一個自定義註解
<code>@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic @interface HandlerType { String value();}/<code>
2、創建一個 審核類型 抽象類
<code>/** * @Description: 審核類型 抽象類 */public abstract class AbstractHandler { //抽象處理方法 abstract public void handle(CheckResultReq req);}/<code>
3、將每一個 審核類型 都創建為一個實現類並繼承抽象類(以賬戶開戶為例子),同時加上自定義註解
<code>/** * @Description: 賬戶開戶 */@Slf4j@Component@HandlerType("ACCOUNT_OPEN")public class AccountOpenHandler extends AbstractHandler { //將該狀態的處理邏輯移到類中處理 @Override public void handle(CheckResultReq req) { // 審核通過 if (CheckTypeEnum.ACCOUNT_OPEN.getCode().equals(req.getCheckType())){ //新增賬戶邏輯 }else { log.info("審核不通過"); }}/<code>
目的:通過使用狀態設計模式後將判斷邏輯分散到多個審核類型實現類中,每個實現類專注於處理自己的邏輯,而自定義註解則是為後續操作做準備。
二、創建一個處理上下文
- 創建一個處理器上下文(其主要職責為存放信息及提供一個實例化Bean的方法)
- 創建一個GetBeanTool工具類(提供一個方法,使非spring管理的類通過該工具可獲取註冊到spring的Bean)
創建一個獲取Spring容器中Bean的工具。Tip:由於非Spring管理的類沒有被component-scan掃描加載所以沒有注入所依賴的Bean。非Spring管理的類想獲取容器內的Bean可通過實現ApplicationContextAware接口獲取Spring容器,再通過getBean()的方式獲取
<code>/** * @Description: 工具的用途--使非spring管理的類通過該工具可獲取註冊到spring的Bean */@Componentpublic class GetBeanTool implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext context) throws BeansException { if (applicationContext == null) { applicationContext = context; } } public staticT getBean(Class /<code>clazz) { return applicationContext.getBean(clazz); }}
創建一個處理上下文
<code>/** * @Description: 處理器容器 */public class HandlerContext { //存放信息 private Map<string> handlerMap; public HandlerContext(Map<string> handlerMap) { } //獲取Bean public AbstractHandler getInstance(String type) { Class clazz = handlerMap.get(type); if (clazz == null) { throw new IllegalArgumentException("not found handler for type: " + type); } return (AbstractHandler)applicationContext.getBean(clazz); }}/<string>/<string>/<code>
目的:通過藉助 處理上下文 來實現類型管理和獲取Bean。
三、實現BeanFactoryPostProcessor的後置處理器進行預處理
- 創建一個HandlerProcessor並實現BeanFactoryPostProcessor
- 掃描審核類型實現類,並將信息裝載入HashMap中
- 創建一個HandlerContext容器,將HashMap存入容器中,並將HandlerContext容器交給Spring管理
創建一個HandlerProcessor後置處理器,Tip:BeanFactoryPostProcessor是spring容器啟動時暴露給用戶的一個擴展點,允許用戶在spring創建bean之前做修改或動態添加bean等
<code>/** * @Description: BeanFactory後置處理器 */@Componentpublic class HandlerProcessor implements BeanFactoryPostProcessor { private static final String HANDLER_PACKAGE = "這裡指定要掃描的包路徑"; //掃描指定路徑下的所有擁有HandlerType註解的class,並將信息存入到一個HashMap<string>中 @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { //自定義設定HashMap初始容量,閱讀HashMap源碼可得知最好預先指定hashmap的size為2的整數次冪次方 //關於HashMap這裡不做詳解,不過其組成結構和擴容機制是不錯的知識點,感興趣的朋友可以深入瞭解學習 Map<string> handlerMap = new HashMap(8); ClassScaner.scan(HANDLER_PACKAGE, HandlerType.class).forEach(clazz -> { String type = clazz.getAnnotation(HandlerType.class).value(); handlerMap.put(type, clazz); }); //創建一個HandlerContext容器,並將HandlerContext HandlerContext context = new HandlerContext(handlerMap); beanFactory.registerSingleton(HandlerContext.class.getName(), context); }}/<string>/<string>/<code>
通過該處理器在Spring啟動時會掃描指定包下包含指定註解的類,並將信息裝載入HashMap中。創建一個 處理上下文 同時交由Spring管理,可方便其他Bean方便地注入使用。下面討論關於掃描器ClassScaner的詳情:
<code>/*** 類掃描器*/public class ClassScaner implements ResourceLoaderAware { private final List<typefilter> includeFilters = new LinkedList<typefilter>(); private final List<typefilter> excludeFilters = new LinkedList<typefilter>(); private ResourcePatternResolver resourcePatternResolver; private MetadataReaderFactory metadataReaderFactory; //掃描方法 @SafeVarargs public static Set<class>> scan(String[] basePackages, Class extends Annotation>... annotations) { ClassScaner cs = new ClassScaner(); //設定要過濾的註解,存入列表中 if (ArrayUtils.isNotEmpty(annotations)) { for (Class anno : annotations) { cs.addIncludeFilter(new AnnotationTypeFilter(anno)); } } Set<class>> classes = new HashSet<>(); //掃描指定包路徑下的類,將符合條件的Class存入Set集合中 for (String s : basePackages) { classes.addAll(cs.doScan(s)); } return classes; } @SafeVarargs public static Set<class>> scan(String basePackages, Class extends Annotation>... annotations) { return ClassScaner.scan(StringUtils.tokenizeToStringArray(basePackages, ",; \\t\\n"), annotations); } public final ResourceLoader getResourceLoader() { return this.resourcePatternResolver; } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourcePatternResolver = ResourcePatternUtils .getResourcePatternResolver(resourceLoader); this.metadataReaderFactory = new CachingMetadataReaderFactory( resourceLoader); } public void addIncludeFilter(TypeFilter includeFilter) { this.includeFilters.add(includeFilter); } public void addExcludeFilter(TypeFilter excludeFilter) { this.excludeFilters.add(0, excludeFilter); } public void resetFilters(boolean useDefaultFilters) { this.includeFilters.clear(); this.excludeFilters.clear(); } //執行掃描 public Set<class>> doScan(String basePackage) { Set<class>> classes = new HashSet<>(); try { String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + org.springframework.util.ClassUtils .convertClassNameToResourcePath(SystemPropertyUtils .resolvePlaceholders(basePackage)) + "/**/*.class"; //獲取指定路徑下的資源 Resource[] resources = this.resourcePatternResolver .getResources(packageSearchPath); //遍歷,將擁有指定註解的Class加入到Set集合中 for (int i = 0; i < resources.length; i++) { Resource resource = resources[i]; if (resource.isReadable()) { MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); if ((includeFilters.size() == 0 && excludeFilters.size() == 0) || matches(metadataReader)) { try { classes.add(Class.forName(metadataReader .getClassMetadata().getClassName())); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } } catch (IOException ex) { throw new BeanDefinitionStoreException( "I/O failure during classpath scanning", ex); } return classes; } protected boolean matches(MetadataReader metadataReader) throws IOException { for (TypeFilter tf : this.excludeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return false; } } for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, this.metadataReaderFactory)) { return true; } } return false; }}/<class>/<class>/<class>/<class>/<class>/<typefilter>/<typefilter>/<typefilter>/<typefilter>/<code>
四、優化CheckResultHandleManager的deal()方法
<code>public class CheckResultHandleManager { @Resource private HandlerContext handlerContext; //化繁為簡 public void deal(CheckResultReq req) { AbstractHandler handler = handlerContext.getInstance(dto.getAuditType()); handler.handle(dto); }}/<code>
最後
至此整個優化過程結束,優化目的是為減少deal()方法中的判斷分支,代碼雖然變複雜但其遵循開放-封閉原則,變得更佳靈活且可擴展。其中結合了後置處理器用於預操作、實現ApplicationContextAware用於獲取註冊在Spring容器中Bean、實現ResourceLoaderAware來獲取資源製作類掃描器等。在優化的過程中也涉及到了很多知識點,是一個不錯的成長方式。以上例子的優化方式不一定在其他地方是最優解決方案,需要結合實際場景對優化方案進行調整才能達到優化效果。優化並不是代碼越複雜越好,而是使其更具靈活和拓展性。
感謝您的閱讀,如果喜歡本文歡迎關注和轉發,本頭條號將堅持原創,持續分享IT技術知識。對於文章內容有其他想法或意見建議等,歡迎提出共同討論共同進步
閱讀更多 聚IT 的文章