由于业务中经常有需要判断的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

 @Data
public class OrderDTO { private String code; private BigDecimal price; /**
* 订单类型
* 1:普通订单
* 2:团购订单
* 3:促销订单
*/
private String orderType;
}

定义一个抽象类的接口:

IHandlerService
 /**
* <p>Title: com.aier.cloud.biz.simplify</p>
* <p>Company:爱尔集团信息中心</p>
* <p>Copyright:Copyright(c)</p>
* User: duanm
* Date: 2019/10/31 15:33
* Description: No Description
*/
public interface IHandlerService { String handler(OrderDTO orderDTO);
}

AbstractHandlerService:抽象类

 public abstract class AbstractHandlerService  implements IHandlerService {

     abstract public String handler(OrderDTO orderDTO);

 }

2、根据需求,将同类型的操作抽象成一个一个产品,继承第一步的抽象类,并实现抽象方法,编写自己的业务逻辑,注意此类需要注入spring容器;

团购订单处理类:

 @Component
@HandlerType(value = "2")
public class GroupHandler extends AbstractHandlerService { @Override
public String handler(OrderDTO orderDTO) {
return "处理团购订单";
}
}

普通订单处理类:

 @Component
@HandlerType(value = "1")
public class NormalHandler extends AbstractHandlerService { @Override
public String handler(OrderDTO orderDTO) {
return "处理普通订单";
}
}

促销订单处理类:

 @Component
@HandlerType(value = "3")
public class PromotionHandler extends AbstractHandlerService {
@Override
public String handler(OrderDTO orderDTO) {
return "处理促销订单";
}
}

注意事项:必须添加  @Component  注解,注入Spring容器      下面编写自定义的注解实现  HandlerType

3、自定义一个类级别注解,用来区分不同操作类型的标识,此自定义标识要有返回一个类型的属性;

 @Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface HandlerType {
String value();
}

4、 抽象出来一个公共HandlerContext对象

抽象出来一个处理所有产品的公共HandlerContext对象,此对象提供一个获取具体产品类的方法,该方法有个入参用于表明是具体那个产品,同时该HandlerContext对象还具有Map类型的属性变量,
存储key为具体的类型,value为具体的产品类对象,该Map类型变量初始化通过构造函数的方式注入;

 public class HandlerContext {

     private Map<String, Class> handlerMap;

     public HandlerContext(Map<String, Class> handlerMap) {
this.handlerMap = handlerMap;
} public AbstractHandlerService getInstance(String type) {
Class clazz = handlerMap.get(type);
if (clazz == null) {
throw new IllegalArgumentException("not found handler for type : " + type);
}
return (AbstractHandlerService) BeanTool.getBean(clazz);
}
}

5、编写一个加载所有产品类的全局process类,用于扫描加了注解@HandlerType的所有实现产品,给存储key 和 value产品对象Map赋值,初始化HandlerContext 将其注册到spring容器中;

 @Component
public class HandlerProcessor implements BeanFactoryPostProcessor { private static final String HANDLER_PACKAGE = "com.aier.cloud.biz.simplify"; /**
* 扫描@HandlerType,初始化HandlerContext 将其注册到spring容器中
*
* @param configurableListableBeanFactory
* @throws BeansException
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
Map<String, Class> handlerMap = Maps.newHashMap(); ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
provider.addIncludeFilter(new AnnotationTypeFilter(HandlerType.class));
Set<BeanDefinition> candidateComponents = provider.findCandidateComponents(HANDLER_PACKAGE);
candidateComponents.forEach(Beanclass -> {
try {
Class<?> clazz = Class.forName(Beanclass.getBeanClassName());
//获取注解中的类型值
String type = clazz.getAnnotation(HandlerType.class).value();
//将注解中的类型值做为key,对应的类作为value 保存在handlerMap中
handlerMap.put(type, clazz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}); //初始化HandlerContext类,将其注入到Spring容器中
HandlerContext handlerContext = new HandlerContext(handlerMap);
configurableListableBeanFactory.registerSingleton(HandlerContext.class.getName(), handlerContext); }
}

注意:主要使用了spring的资料加载工具类,把所有的产品实现类都扫描 存储到map中,并利用继承  BeanFactoryPostProcessor   通过实现它的方法,动态的注入 HandlerContext 进入spring容器

自定义注解和抽象处理器都很简单,那么如何将处理器注册到spring容器中呢?

具体思路是:

1、扫描指定包中标有@HandlerType的类;

2、将注解中的类型值作为key,对应的类作为value,保存在Map中;

3、以上面的map作为构造函数参数,初始化HandlerContext,将其注册到spring容器中;

几个关键的工具类:

 @Component
public class SpringContextUtils implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtils.applicationContext = applicationContext;
} /**
* 取得存储在静态变量中的ApplicationContext.
*/
public static ApplicationContext getApplicationContext() {
checkApplicationContext();
return applicationContext;
} /**
* 清除applicationContext静态变量.
*/
public static void cleanApplicationContext() {
applicationContext = null;
} private static void checkApplicationContext() {
if (applicationContext == null) {
throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义SpringContextHolder");
}
} /**
* 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
checkApplicationContext();
return (T) applicationContext.getBean(name);
} /**
* 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(Class<T> clazz) {
checkApplicationContext();
return (T) applicationContext.getBeansOfType(clazz);
}
}
 public class BeanTool {

     public static  <T> T getBean(Class<T> clazz) {
String clazzName = clazz.getName();
clazzName = clazzName.replace(".",",");
String[] split = clazzName.split(",");
String name = split[split.length - 1];
return SpringContextUtils.getBean(lowerFirst(name));
} public static String lowerFirst(String oldStr) {
char[] chars = oldStr.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
}

注意此处可以优化下,可以简单来获取类的名字,

 public static  <T> T getBean(Class<T> clazz) {
//获取类的名字
String simpleName = clazz.getSimpleName();
//根据类的名称获取类的实列对象
return SpringContextUtils.getBean(lowerFirst(simpleName));
}

思考:  还是JAVA反射 不太熟悉,导致走了弯路来处理,JAVA反射还是要吃透,多写写。

(1)反射机制极大的提高了程序的灵活性和扩展性,降低模块的耦合性,提高自身的适应能力。

(2)通过反射机制可以让程序创建和控制任何类的对象,无需提前硬编码目标类。
(3)使用反射机制能够在运行时构造一个类的对象、判断一个类所具有的成员变量和方法、调用一个对象的方法。
(4)反射机制是构建框架技术的基础所在,使用反射可以避免将代码写死在框架中。

6、测试运行代码,查看结果

测试运行:在service层进行调用,可以如下来编写:

 @Service
public class OrderServiceImpl implements IOrderService { @Resource
private HandlerContext handlerContext; @Override
public String handle(OrderDTO orderDTO) {
IHandlerService instance = handlerContext.getInstance(orderDTO.getOrderType());
System.out.println(instance.handler(orderDTO));
return instance.handler(orderDTO);
}
}

运行结果如下:

最后请注意一点,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中拿到具体的实现类型,完成调用;

springBoot中怎么减少if---else,怎么动态手动注册类进入Spring容器的更多相关文章

  1. SpringBoot 之 普通类获取Spring容器中的bean

    [十]SpringBoot 之 普通类获取Spring容器中的bean   我们知道如果我们要在一个类使用spring提供的bean对象,我们需要把这个类注入到spring容器中,交给spring容器 ...

  2. [十]SpringBoot 之 普通类获取Spring容器中的bean

    我们知道如果我们要在一个类使用spring提供的bean对象,我们需要把这个类注入到spring容器中,交给spring容器进行管理,但是在实际当中,我们往往会碰到在一个普通的Java类中,想直接使用 ...

  3. Spring Boot中普通类获取Spring容器中的Bean

    我们知道如果我们要在一个类使用spring提供的bean对象,我们需要把这个类注入到spring容器中,交给spring容器进行管理,但是在实际当中,我们往往会碰到在一个普通的Java类中,自己动手n ...

  4. SpringBoot中并发定时任务的实现、动态定时任务的实现(看这一篇就够了)

    原创不易,如需转载,请注明出处https://www.cnblogs.com/baixianlong/p/10659045.html,否则将追究法律责任!!! 一.在JAVA开发领域,目前可以通过以下 ...

  5. elastic-job 分布式定时任务框架 在 SpringBoot 中如何使用(二)动态添加任务需求

    之前一篇用过了如何在使用创建最简单的任务:比如每天定时清空系统的缓存 这篇文章主要讲解:如何运用elastic-job-lite做灵活的细粒度任务,比如: 如何定时取消某个订单在下订单后30分钟未支付 ...

  6. 使用反射获取类中的属性(可用于动态返回PO类的列,当做表格的表头)

    //利用反射取类中的属性字段 try { Class clazz = Class.forName("houji.bean.model.TaskModel"); Field[] fi ...

  7. SpringBoot之普通类获取Spring容器中的bean

    package com.geostar.geostack.git_branch_manager.common; import org.springframework.beans.BeansExcept ...

  8. FastJson序列化Json自定义返回字段,普通类从spring容器中获取bean

    前言: 数据库的字段比如:price:1 ,返回需要price:1元. 这时两种途径修改: ① 比如sql中修改或者是在实体类转json前遍历修改. ②返回json,序列化时候修改.用到的是fastj ...

  9. Spring Retry 在SpringBoot 中的应用

    Spring Boot中使用Spring-Retry重试框架 Spring Retry提供了自动重新调用失败的操作的功能.这在错误可能是暂时的(例如瞬时网络故障)的情况下很有用. 从2.2.0版本开始 ...

随机推荐

  1. Educational Codeforces Round 37 (Rated for Div. 2)C. Swap Adjacent Elements (思维,前缀和)

    Educational Codeforces Round 37 (Rated for Div. 2)C. Swap Adjacent Elements time limit per test 1 se ...

  2. java线程基础巩固---创建并启动线程

    对于java的并发编程方面的东东,不管是面试还是实际工作开发都是非常重要的,而往往只要涉及到并发相关的东东有点让人觉得有点难,而实际工作中涉及到并发可能就是简单的用下同步块.上锁之类的一些简单的操作, ...

  3. 【洛谷P4430】小猴打架

    题目大意:求带标号 N 个点的生成树个数,两棵生成树相同当且仅当两棵树结构相同且边的生成顺序相同. 题解:学会了 prufer 序列. prufer 序列是用来表示带标号的无根树的序列. 每种不同类型 ...

  4. centos 7 + Net Core 3.0 + Docker 配置说明(不含https)

    1.新建Core3.0项目 1.1 使用visual studio 2019 创建一个名为core3.web.httpapi 的"ASP.NET Core Web应用程序" 1.2 ...

  5. jQuery 查找父节点 parents()与closest()

    parents()由内向外,直到最高的父节点停止查找,返回的父节点是多个 closest()由内向外查找,当找到符合规则的一个,则不再查找,返回的是0或1个

  6. 多项式乘法(FFT)模板 && 快速数论变换(NTT)

    具体步骤: 1.补0:在两个多项式最前面补0,得到两个 $2n$ 次多项式,设系数向量分别为 $v_1$ 和 $v_2$. 2.求值:用FFT计算 $f_1 = DFT(v_1)$ 和 $f_2=DF ...

  7. Codeforces Round #586 (Div. 1 + Div. 2) B. Multiplication Table

    链接: https://codeforces.com/contest/1220/problem/B 题意: Sasha grew up and went to first grade. To cele ...

  8. vue04

    目录 Vue项目环境 项目的创建 vue根据配置重新构建依赖 pycharm管理vue项目 vue项目目录结构分析 vue项目生命周期 views文件夹内的.vue文件介绍 配置自定义全局样式 导航栏 ...

  9. 基于sed 的猫狗游戏

    1.测试文件 [root@L shells]# cat catDog.txt snake snake pig bird dog cat snake pig bird snake cat bird do ...

  10. docker国内镜像地址

    https://registry.docker-cn.com http://hub-mirror.c.163.com https://docker.mirrors.ustc.edu.cn