策略模式+Spring——讓我們的代碼更加高大上一點

if…else if…else 代碼是實際的項目代碼中出現的比例還是蠻高的,特別是針對一些業務需求根據不同類型來進行不同的業務處理,針對這種業務模型,我們來試着使用策略模式結合Spring來優化我們的代碼,讓代碼更加高大上一點

爲了更好得結合業務來實現代碼,先簡單的介紹一下具體的業務邏輯模型:

目前有一個功能,需要打印表單設置的內容,處理表單固有的屬性之外,用戶可以維護自定義屬性,每個自定義屬性可以設置不同的處理業務邏輯,打印時,根據自定義屬性來處理相關的數據,並且返回數據

上面就是簡單的具體的業務模型,我們將業務具體到代碼中

    /**
     * 定義自定義屬性處理根據不同的類型來處理的接口
     */
    public interface CustomerPropertiesHandler {
        String handle(Object data);
    }
    
    /**
     * 自定義屬性合計運算處理
     */    
    public class ArithmeticPropertySummationHandle implement CustomerPropertiesHandler {
        @Override
        public String handle(Object data) {
            return "合計運算";
        }
    }
    
    /**
     * 自定義屬性相乘後合計運算處理
     */  
    public class ArithmeticMultiPropertySummationHandle implement CustomerPropertiesHandler {
        @Override
        public String handle(Object data) {
            return "相乘後合計運算";
        }
    }
    
    /**
    * 自定義屬性處理
    */
    public class CustomerPropertiesHandle {
        /**
         *  打印的數據
         */
        private Object data;
        
        public CustomerPropertiesHandle(Object data) {
            this.data = data;
        }
        
        public CustomerPropertiesHandle() {}
         /**
          * 處理
          */
        public String handle(String type) {
            if ("1".equals(type)){
                return new ArithmeticPropertySummationHandle().handle(data);
            } else if ("2".equals(type)) {
                return new ArithmeticMultiPropertySummationHandle().handle(data);
            } else {
                return null;
            }
        }
    }
    

上面就是我們根據業務模型編寫的僞代碼,這樣的代碼 如果新增了新的自定義屬性的運算規則,則需要修改CustomerPropertiesHandle這個類,上面只是簡單的代碼實現,如果代碼邏輯複雜的話,相應的代碼變動就會增加風險

通過業務模型分析,我們需要將處理的模型固定不變,對於自定義屬性的運算的變更,應該是不影響CustomerPropertiesHandle的代碼實現,這樣的代碼符合開閉原則,也是本博文需要實現的

上面的僞代碼已經使用了策略模式,本博文的主題是使用策略模式和Spring整合在一起來實現上面的業務,所以我們現在需要繼承Spring的環境,爲了方便,我們使用SpringBoot來實現,對應的版本是:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

思路

從上面的代碼中,我們可以看出代碼需要知道類型才能去判斷調用哪種處理器來處理,so:

思路:提供一個方法通過類型來獲取對應的處理器

我們的思路有了,但是怎麼來是實現呢,這裏我們可以利用Spring的特性,Spring在啓動之後,解析對應的bean保存到內存中,我們這裏需要處理下,在Spring的Bean容器中獲取自定義屬性對應的處理器,並且組裝起來方便獲取,最好是使用單例模式來確保線程安全性

有了對應的思路,我們就來實現相應的代碼。
首先定義自定義屬性的處理公共接口以及抽象類

    /**
     * Copyright © 2018 五月工作室. All rights reserved.
     *
     * @Project: stuff
     * @ClassName: CustomerPropertiesHandler
     * @Package: com.amos.stuff.proxyandspring.biz
     * @author: zhuqb
     * @Description: 定義自定義屬性處理的入口方法
     * 所有自定義屬性處理器都需要實現該接口
     * @date: 2019/9/26 0026 下午 14:05
     * @Version: V1.0
     */
    public interface CustomerPropertiesHandler {
        /**
         * 獲取自定義屬性的類型
         * 該還有種方法是可以自定義註解來指定自定義屬性的類型
         * 方法是 在該接口的實現類中添加註解,註解中指定該自定義屬性的類型
         * 接着在 CustomPropertiesTypeBeanInitialization 中通過反射獲取所有含有該註解的類
         * 最後再組裝對應的map數據(參考CustomPropertiesTypeBeanInitialization)
         *
         * @return 返回值直接使用自定義屬性類型的枚舉
         */
        CustomerPropertiesTypeEnum type();
    
        /**
         * 自定義屬性處理
         * 實現類來處理對應的業務邏輯
         *
         * @param data 需要處理的數據
         * @return
         */
        String handler(Object data);
    }

    /**
     * Copyright © 2018 五月工作室. All rights reserved.
     *
     * @Project: stuff
     * @ClassName: AbstractCustomerPropertiesHandler
     * @Package: com.amos.stuff.proxyandspring.biz
     * @author: zhuqb
     * @Description: 通用的自定義屬性運算處理
     * 新增這一層是爲了拓展每個運算屬性中通用的功能,減少代碼量
     * @date: 2019/9/26 0026 上午 11:48
     * @Version: V1.0
     */
    @Component
    public abstract class AbstractCustomerPropertiesHandler implements CustomerPropertiesHandler {
        public final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    }

然後是自定義屬性類型的各種不同的處理實現(這裏沒有具體的處理的內容,只有簡化後的結構模型)

    /**
     * Copyright © 2018 五月工作室. All rights reserved.
     *
     * @Project: stuff
     * @ClassName: CustomePropertiesArithmeticMultiSummationHandler
     * @Package: com.amos.stuff.proxyandspring.biz.impl
     * @author: zhuqb
     * @Description: 相乘後合計運算
     * @date: 2019/9/26 0026 下午 15:07
     * @Version: V1.0
     */
    @Component
    public class CustomePropertiesArithmeticMultiSummationHandler extends AbstractCustomerPropertiesHandler {
        @Override
        public CustomerPropertiesTypeEnum type() {
            return CustomerPropertiesTypeEnum.arithmeticMultiSummation;
        }
    
        @Override
        public String handler(Object data) {
            this.logger.info("相乘後合計運算");
            return "相乘後合計運算";
        }
    }
    
    /**
     * Copyright © 2018 五月工作室. All rights reserved.
     *
     * @Project: stuff
     * @ClassName: CustomePropertiesSummationHandler
     * @Package: com.amos.stuff.proxyandspring.biz.impl
     * @author: zhuqb
     * @Description: 合計運算處理器
     * @date: 2019/9/26 0026 下午 14:52
     * @Version: V1.0
     */
    @Component
    public class CustomePropertiesSummationHandler extends AbstractCustomerPropertiesHandler {
        @Override
        public String handler(Object data) {
            this.logger.info("合計運算");
            return "合計運算";
        }
    
        @Override
        public CustomerPropertiesTypeEnum type() {
            return CustomerPropertiesTypeEnum.summation;
        }
    }
    
    /**
 * Copyright © 2018 五月工作室. All rights reserved.
 *
 * @Project: stuff
 * @ClassName: CustomerPropertiesTypeEnum
 * @Package: com.amos.stuff.proxyandspring.biz.type
 * @author: zhuqb
 * @Description: 自定義屬性類型枚舉
 * 如果需要新增屬性時,只需要在此枚舉中新增對應的屬性即可
 * @date: 2019/9/26 0026 下午 13:41
 * @Version: V1.0
 */
@AllArgsConstructor
public enum CustomerPropertiesTypeEnum implements BaseEnum {
    /**
     * 合計運算
     */
    summation("summation"),
    /**
     * 相乘後合計運算
     */
    arithmeticMultiSummation("arithmeticMultiSummation");
    String code;

    @Override
    public String getKey() {
        return this.code;
    }

    public static final EnumFindHelper<CustomerPropertiesTypeEnum, String> CODE_HELPER
            = new EnumFindHelper(CustomerPropertiesTypeEnum.class, new EnumFindHelper.EnumKeyGetter<CustomerPropertiesTypeEnum, String>() {

        @Override
        public String getKey(CustomerPropertiesTypeEnum enumValue) {
            return enumValue.code;
        }

    });

    public static CustomerPropertiesTypeEnum getByCode(String code) {
        return CODE_HELPER.find(code);
    }

}

上述代碼中的自定義了EnumFindHelper來實現通過code查找枚舉對象,該操作類的實現可以參考我的相關的博客TODO

上面的代碼其實使用的就是簡化版本地策略模式,只不過還沒有相對應的策略處理模型,接下來我們就來封裝策略處理模型代碼。

按照上面的思路,我們需要在系統啓動後,組裝對應的數據接口,爲什麼需要等到系統啓動之後呢?由於該項目是SpringBoot框架我們的自定義屬性處理器都是注入到Spring的Bean容器中的,我們需要再從Bean容器中獲取到,然後組裝我們需要的數據接口

    /**
     * Copyright © 2018 五月工作室. All rights reserved.
     *
     * @Project: stuff
     * @ClassName: CustomPropertiesTypeBeanInitialization
     * @Package: com.amos.stuff.proxyandspring.biz.util
     * @author: zhuqb
     * @Description: 自定義屬性處理器和自定義屬性類型數據初始化
     * <p>
     * 通過自定義屬性處理接口來獲取所有的自定義屬性處理器
     * 組裝每個自定義屬性處理器對應的類型
     * <p>
     * 以Map結構來存儲
     * <p>
     * 這裏之所以使用枚舉對象是爲了解決hash衝突的現象
     * @date: 2019/9/26 0026 下午 16:07
     * @Version: V1.0
     */
    public class CustomPropertiesTypeBeanInitialization {
        private final static Logger logger = LoggerFactory.getLogger(CustomPropertiesTypeBeanInitialization.class);
    
        /**
         * 首先獲取
         *
         * @return
         */
        public static Map<CustomerPropertiesTypeEnum, CustomerPropertiesHandler> getMap() {
            logger.info("init...");
    
            Map<CustomerPropertiesTypeEnum, CustomerPropertiesHandler> map
                    = new HashMap<>(CustomerPropertiesTypeEnum.values().length);
    
            // 首先獲取 CustomerPropertiesHandler 接口的所有的實現類
            Map<String, CustomerPropertiesHandler> clazzes = SpringContext.getApplicationContext().getBeansOfType(CustomerPropertiesHandler.class);
            
            for (Map.Entry<String, CustomerPropertiesHandler> entry : clazzes.entrySet()) {
    
                logger.info("解析到的class文件:{}", entry.getValue().getClass());
                CustomerPropertiesHandler handler = SpringContext.getBean(entry.getValue().getClass());
                map.put(handler.type(), handler);
            }
            logger.info("解析後的customPropertiesMap:{}", JSONObject.toJSONString(map));
            return map;
        }

操作類有了,這裏我們可以使用單例模式來獲取這個Map接口,然後在系統啓動的時候來調用實例化單例

    /**
     * Copyright © 2018 五月工作室. All rights reserved.
     *
     * @Project: stuff
     * @ClassName: CustomePropertiesTypeSingleton
     * @Package: com.amos.stuff.proxyandspring
     * @author: zhuqb
     * @Description: 自定義屬性類型單例
     * @date: 2019/9/26 0026 下午 15:19
     * @Version: V1.0
     */
    public class CustomePropertiesTypeSingleton {
    
        private CustomePropertiesTypeSingleton() {
        }
    
        private static class Singleton {
            private static Map<CustomerPropertiesTypeEnum, CustomerPropertiesHandler> map;
    
            static {
    
                map = CustomPropertiesTypeBeanInitialization.getMap();
            }
    
            public static Map<CustomerPropertiesTypeEnum, CustomerPropertiesHandler> getInstance() {
                return map;
            }
        }
    
        public static Map<CustomerPropertiesTypeEnum, CustomerPropertiesHandler> getInstance() {
            return Singleton.getInstance();
        }
    }
    
    
    /**
     * Copyright © 2018 五月工作室. All rights reserved.
     *
     * @Project: stuff
     * @ClassName: CustomPropertiesTypeRunner
     * @Package: com.amos.stuff.proxyandspring.runner
     * @author: zhuqb
     * @Description:
     * @date: 2019/9/26 0026 下午 16:38
     * @Version: V1.0
     */
    @Component
    public class CustomPropertiesTypeRunner implements ApplicationRunner {
    
        public final Logger logger = LoggerFactory.getLogger(this.getClass());
    
        @Override
        public void run(ApplicationArguments args) throws Exception {
            this.logger.info("自定義屬性類型初始化");
            Map<CustomerPropertiesTypeEnum, CustomerPropertiesHandler> map = CustomePropertiesTypeSingleton.getInstance();
            CustomerPropertiesHandler handler = map.get(CustomerPropertiesTypeEnum.arithmeticMultiSummation);
            handler.handler(null);
        }
    }

這樣我們就完成了在系統啓動的時候完成了自定義屬性處理器和自定義屬性類型的Map接口,接下來我們來測試下是否滿足我們的需求,編寫測試方法

    /**
     * Copyright © 2018 五月工作室. All rights reserved.
     *
     * @Project: stuff
     * @ClassName: CustomPropertiesController
     * @Package: com.amos.stuff.proxyandspring.web
     * @author: zhuqb
     * @Description: 自定義屬性Controller
     * @date: 2019/9/27 0027 上午 9:51
     * @Version: V1.0
     */
    @RestController
    @RequestMapping(value = "/custom-properties")
    public class CustomPropertiesController extends BaseController {
    
        @GetMapping(value = "/handle/{handler}")
        public Result customProperties(@PathVariable("handler") CustomerPropertiesTypeEnum typeEnum) {
            this.logger.info(typeEnum.toString());
            return ResultWapper.success(CustomePropertiesTypeSingleton.getInstance().get(typeEnum).handler(null));
        }
    }

上面代碼中的參數爲枚舉類型,SpringBoot對接收參數爲枚舉類型的,需要轉換下類型,這裏我們進行如下設置,代碼不做詳細說明,博友可以參考網上相關配置說明:

    /**
     * Copyright © 2018 五月工作室. All rights reserved.
     *
     * @Project: stuff
     * @ClassName: EnumConverterFactory
     * @Package: com.amos.stuff.common.service
     * @author: zhuqb
     * @Description:
     * @date: 2019/9/27 0027 上午 10:35
     * @Version: V1.0
     */
    public class EnumConverterFactory implements ConverterFactory<String, BaseEnum> {
    
        private static Map<Class, Converter> convertMap = new ConcurrentHashMap<>();
    
        @Override
        public <T extends BaseEnum> Converter<String, T> getConverter(Class<T> aClass) {
            Converter converter = convertMap.get(aClass);
            if (null == converter) {
                converter = new EnumConverter(aClass);
                convertMap.put(aClass, converter);
            }
            return converter;
        }
    
        class EnumConverter<T extends BaseEnum> implements Converter<String, T> {
    
            private Class<T> clazz;
            private Map<String, T> enumMap = new ConcurrentHashMap<>();
    
    
            public EnumConverter(Class<T> clazz) {
                this.clazz = clazz;
    
                T[] enums = clazz.getEnumConstants();
                for (T anEnum : enums) {
                    this.enumMap.put(anEnum.getKey(), anEnum);
                }
            }
    
            @Override
            public T convert(String key) {
                T result = this.enumMap.get(key);
    
                if (null == result) {
                    throw new IllegalArgumentException("params is wrong");
                }
    
                return result;
            }
        }
    }
    /**
     * Copyright © 2018 五月工作室. All rights reserved.
     *
     * @Project: stuff
     * @ClassName: WebAppConfigurer
     * @Package: com.amos.stuff.common.config
     * @author: zhuqb
     * @Description:
     * @date: 2019/9/27 0027 上午 10:49
     * @Version: V1.0
     */
    @Configuration
    public class WebAppConfigurer implements WebMvcConfigurer {
        @Override
        public void addFormatters(FormatterRegistry registry) {
            registry.addConverterFactory(new EnumConverterFactory());
        }
    }

接下來我們使用postman來執行測試下,應該可以看到想要的結果了

總結

如果需要接入我們上面的代碼,需要添加一種自定義屬性處理器則需要如下的操作

  • CustomerPropertiesTypeEnum類中添加自定義屬性類型
  • 新增繼承 AbstractCustomerPropertiesHandler的對應的自定義屬性處理器,並且在type方法中返回自定義屬性類型

完成以上兩步的接入就可以使用了

詳細代碼,可以參考我的Gitee

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章