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来实现,对应的版本是:
org.springframework.boot
spring-boot-starter-parent
2.1.3.RELEASE
思路
从上面的代码中,我们可以看出代码需要知道类型才能去判断调用哪种处理器来处理,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 CODE_HELPER
= new EnumFindHelper(CustomerPropertiesTypeEnum.class, new EnumFindHelper.EnumKeyGetter() {
@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: 自定义属性处理器和自定义属性类型数据初始化
*
* 通过自定义属性处理接口来获取所有的自定义属性处理器
* 组装每个自定义属性处理器对应的类型
*
* 以Map结构来存储
*
* 这里之所以使用枚举对象是为了解决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 getMap() {
logger.info("init...");
Map map
= new HashMap<>(CustomerPropertiesTypeEnum.values().length);
// 首先获取 CustomerPropertiesHandler 接口的所有的实现类
Map clazzes = SpringContext.getApplicationContext().getBeansOfType(CustomerPropertiesHandler.class);
for (Map.Entry 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 map;
static {
map = CustomPropertiesTypeBeanInitialization.getMap();
}
public static Map getInstance() {
return map;
}
}
public static Map 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 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 {
private static Map convertMap = new ConcurrentHashMap<>();
@Override
public Converter getConverter(Class aClass) {
Converter converter = convertMap.get(aClass);
if (null == converter) {
converter = new EnumConverter(aClass);
convertMap.put(aClass, converter);
}
return converter;
}
class EnumConverter implements Converter {
private Class clazz;
private Map enumMap = new ConcurrentHashMap<>();
public EnumConverter(Class 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