文章目录
有时候写多了if-else自己都会烦, 为什么别人的代码这么简洁, 而自己写出来的代码这么的臃肿,一堆一堆的,看起来很难受,这里我们介绍几种比较好的方法可以让你代码更简洁优雅一点
首先举个业务案例
需求:我们都知道一个请求过来, 请求头会带着Content-Type
这个字段, 我们这里就是根据这个Content-Type
的类型来做不同的内容解析, 如果匹配到Content-Type=json
类型我们就输出一条json相关的输出信息, 如果匹配到Content-Type=xml
类型我们就输出一条xml相关的输出信息
Content-Type
定义两个枚举值如下, json类型代表我们要按照json格式输出, xml类型代表我们要按照xml格式输出
@Getter
public enum ContentType {
/**代表application/json请求类型,内容应该按照json处理解析*/
APPLICATION_JSON("json"),
/**代表application/xml请求类型,内容应该按照xml处理解析*/
APPLICATION_XML("xml");
private final String code;
ContentType(final String code) {
this.code = code;
}
}
定义好了上面的枚举, 我们开始定义我们真正开始要执行的业务操作,Service
以及两个ServiceImpl
类
JsonContentResolverServiceImpl
对Content-Type=json类型的做内容解析XmlContentResolverServiceImpl
对Content-Type=xml类型的做内容解析
public interface ContentResolverService {
void doResolver(NativeRequest request);
}
@Service
public class JsonContentResolverServiceImpl implements ContentResolverService {
@Override
public void doResolver(NativeRequest request) {
System.out.println("因为请求是contentType=JSON,所以按照<JSON>方式显示内容,contentType="+request.getContentType());
}
}
@Service
public class XmlContentResolverServiceImpl implements ContentResolverService {
@Override
public void doResolver(NativeRequest request) {
System.out.println("因为请求是contentType=XML,所以按照<XML>方式显示内容,contentType="+request.getContentType());
}
}
接着我们要开始组装这个业务(根据Content-Type类型对应进行不同的处理解析内容)
如下图所示:我们会发现这extracted
方法会产生很多的判断if-else判断条件,设想下有很多的Content-Type是不是就要写n多的if-else, 而且还要改源代码,破坏了代码的开闭原则
,写起来也很烦!
@Autowired
private JsonContentResolverServiceImpl jsonService;
@Autowired
private XmlContentResolverServiceImpl xmlService;
private void extracted(SendMqRequest request) {
if (request.getContentType().equals(ContentType.APPLICATION_JSON.getCode())) {
jsonService.doResolver(NativeRequest.builder()
.contentType(ContentType.APPLICATION_JSON.getCode())
.build());
} else if(request.getContentType().equals(ContentType.APPLICATION_XML.getCode())) {
xmlService.doResolver(NativeRequest.builder()
.contentType(ContentType.APPLICATION_XML.getCode())
.build());
} else {
//TODO...
}
}
如果想要消除这些if-else,其实我们知道if-else,就是根据某个条件获取正确一个值,有了这个概念, 我么会想到
map集合
,这个是最快获取一个等值条件的容器,它可以帮你的条件和你希望返回的值做了一 一映射关系,
那我们如何将这些条件和bean做一个绑定关系?
如果要想绑定得拿到我们的bean,获取到bean得话,做条件绑定关系就很简单
自定义注解模式(结合事件监听机制)
我们可以把我们的条件(Content-Type
)定义成注解,因为我们要把这个条件作为map得key,然后value放对应得bean, 如下所示:
/**
* 这个注解主要是给我们Spring容器中的bean重新起个别名
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ContentTypeCode {
String contentType() default "";
}
然后在所有得ContentResolverService
实现类添加这个注解
@Service
@ContentTypeCode(contentType = "applicationJson")
public class JsonContentResolverServiceImpl implements ContentResolverService {
@Override
public void doResolver(NativeRequest request) {
System.out.println("因为请求是contentType=JSON,所以按照<JSON>方式显示内容,contentType="+request.getContentType());
}
}
@Service
@ContentTypeCode(contentType = "applicationXml")
public class XmlContentResolverServiceImpl implements ContentResolverService {
@Override
public void doResolver(NativeRequest request) {
System.out.println("因为请求是contentType=XML,所以按照<XML>方式显示内容,contentType="+request.getContentType());
}
}
然后增加最关键的类,我们要首先要获取到bean,以及这个bean对应得自定义注解(其实就是我们得Content-Type
条件),这个bean和注解以及条件都有了,我们就 可以在 map中绑定关系了
获取bean得方式有很多中,我这是其中一种,实现接口监听ApplicationListener
, 监听Spring得ContextRefreshedEvent
事件,这个事件在已经把bean都已经加载完成了,这样在onApplicationEvent
方法中,就可以拿到ApplicationContext
的实例
getBeansWithAnnotation
根据注解获取标记得bean
@Service
public class TransInnerSerivceImpl
implements TransInnerService,
ApplicationListener<ContextRefreshedEvent> {
private void extracted(SendMqRequest request) {
//一行代码就完事了
gobalMap.get(request.getContentType())
.doResolver(NativeRequest.builder().contentType(request.getContentType()).build());
}
private static Map<String,ContentResolverService> gobalMap = new HashMap<>();
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext context = event.getApplicationContext();
Map<String, Object> beansWithAnnotation = context.getBeansWithAnnotation(ContentTypeCode.class);
Optional.ofNullable(beansWithAnnotation).ifPresent(
e->{
e.forEach((k,v) ->{
String contentTypeKey = v.getClass().getAnnotation(ContentTypeCode.class).contentType();
System.out.println("contentTypekey====>"+contentTypeKey);
gobalMap.put(contentTypeKey, (ContentResolverService)v);
});
}
);
}
}
这样每次新加一种Content-Type=Text
都只需要在实现类上加上注解即可,什么都不用做了
注意: 这种方式的注解可以没有业务含义,只要不重复就行。
命名约束模式(实现ApplicationContextAware接口)
其实就是相当于注入一个ApplicationContext
, 也可以直接使用@Autowire
注入更快
//@Autowired
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = context;
}
然后就是通过ApplicationContext.getBean("实现类得名字")
,这里注意一下,我们得实现类我们得自己给他一个名字,就按照你的条件类型来命名即可,如下所示:
@Service("applicationXml")
public class XmlContentResolverServiceImpl implements ContentResolverService {
@Override
public void doResolver(NativeRequest request) {
System.out.println("因为请求是contentType=XML,所以按照<XML>方式显示内容,contentType="+request.getContentType());
}
}
@Service("applicationJson")
public class JsonContentResolverServiceImpl implements ContentResolverService {
@Override
public void doResolver(NativeRequest request) {
System.out.println("因为请求是contentType=JSON,所以按照<JSON>方式显示内容,contentType="+request.getContentType());
}
}
按照一定得命名之后, 我们就可以直接通过ApplicationContext.getBean("命名")
获取对应的bean并调用其方法了,比如我们这里request中 contentType
传入applicationJson,或者applicationXml都可以获取到对应得实现类bean,但是传入其他类型的就不能获取了,因为我们这两个名称applicationXml
,applicationJson
在 XmlContentResolverServiceImpl
,JsonContentResolverServiceImpl
类中标记过
private void extracted(SendMqRequest request) {
((ContentResolverService)context.getBean(request.getContentType())).doResolver(NativeRequest.builder()
.contentType(request.getContentType())
.build());
}
注意:这个约束性比较大 ,主要是自己得规则自己知道就行,就怕被人帮你给改了下就完蛋
工厂模式及策略模式
原理其实还是一样得,就是我们还是得获取到已经完成装载得 bean,然后想方设法把bean获取到,做map
得绑定关系
先定义一个全局的工厂类,绑定方法register
public class MapFactory {
public static Map<String, ContentResolverService> serviceMap = new HashMap<>();
public static void register(String contentType, ContentResolverService bean) {
serviceMap.put(contentType, bean);
}
}
然后每个实现类实现接口InitializingBean
,这个接口会有个方法afterPropertiesSet
这个方法是在bean装载完成之后可以加载的方法,所以这里我们可以做map绑定关系,如下所示:
@Service
public class XmlContentResolverServiceImpl implements ContentResolverService, InitializingBean {
@Override
public void doResolver(NativeRequest request) {
System.out.println("因为请求是contentType=XML,所以按照<XML>方式显示内容,contentType="+request.getContentType());
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("XmlContentResolverServiceImpl 已经被Spring加载好了");
MapFactory.register(ContentType.APPLICATION_XML.getCode(), this);
}
}
@Service
public class JsonContentResolverServiceImpl implements ContentResolverService, InitializingBean {
@Override
public void doResolver(NativeRequest request) {
System.out.println("因为请求是contentType=JSON,所以按照<JSON>方式显示内容,contentType="+request.getContentType());
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("JsonContentResolverServiceImpl 已经被Spring加载好了");
MapFactory.register(ContentType.APPLICATION_JSON.getCode(), this);
}
}
然后我们在逻辑里面直接根据条件获取对应的实现类并调用方法
private void extracted(SendMqRequest request) {
MapFactory.serviceMap.get(request.getContentType()).doResolver(NativeRequest.builder()
.contentType(request.getContentType())
.build());
}
这里用的是最简单的工厂类作为案例
模板方法模式
我们先一起看看spring AOP
的部分源码,看一下DefaultAdvisorAdapterRegistry
的wrap
方法
public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
if (adviceObject instanceof Advisor) {
return (Advisor)adviceObject;
} else if (!(adviceObject instanceof Advice)) {
throw new UnknownAdviceTypeException(adviceObject);
} else {
Advice advice = (Advice)adviceObject;
if (advice instanceof MethodInterceptor) {
return new DefaultPointcutAdvisor(advice);
} else {
Iterator var3 = this.adapters.iterator();
AdvisorAdapter adapter;
do {
if (!var3.hasNext()) {
throw new UnknownAdviceTypeException(advice);
}
adapter = (AdvisorAdapter)var3.next();
} while(!adapter.supportsAdvice(advice));
return new DefaultPointcutAdvisor(advice);
}
}
}
重点看看supportAdvice
方法,有三个类实现(AfterReturningAdviceAdapter
,MethodBeforeAdviceAdapter
,ThrowsAdviceAdapter
)了这个方法。我们随便抽一个类看看
class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {
AfterReturningAdviceAdapter() {
}
public boolean supportsAdvice(Advice advice) {
return advice instanceof AfterReturningAdvice;
}
public MethodInterceptor getInterceptor(Advisor advisor) {
AfterReturningAdvice advice = (AfterReturningAdvice)advisor.getAdvice();
return new AfterReturningAdviceInterceptor(advice);
}
}
该类的supportsAdvice
方法非常简单,只是判断了一下advice
的类型是不是AfterReturningAdvice
我们看到这里应该有所启发。
其实,我们可以这样做,定义一个接口或者抽象类,里面有个support
方法判断参数传的Content-Type
是否自己可以处理,如果可以处理则走内容解析逻辑。
先声明接口,提供bool得support
方法
public interface ContentResolverService {
void doResolver(NativeRequest request);
boolean support(String contentType);
}
然后实现类如下
@Service
public class JsonContentResolverServiceImpl implements ContentResolverService {
@Override
public void doResolver(NativeRequest request) {
System.out.println("因为请求是contentType=JSON,所以按照<JSON>方式显示内容,contentType="+request.getContentType());
}
@Override
public boolean support(String contentType) {
return ContentType.APPLICATION_JSON.getCode().equals(contentType);
}
}
@Service
public class XmlContentResolverServiceImpl implements ContentResolverService {
@Override
public void doResolver(NativeRequest request) {
System.out.println("因为请求是contentType=XML,所以按照<XML>方式显示内容,contentType="+request.getContentType());
}
@Override
public boolean support(String contentType) {
return ContentType.APPLICATION_XML.getCode().equals(contentType);
}
}
我们在声明一个组装类
,这是一个关键得类, 但是方法其实上面已经讲到了,主要是怎么获取到bean,然后取出来之后做绑定关系,实现接口InitializingBean
, 实现接口ApplicationContextAware
(这里我直接使用@Autowire
注入方便快捷, 其实我们看源码得时候 有很多时候都是这种方式实现代码(SpringBoot视图解析器)
注意: 一般得 和业务不想关得业务可以抽离出去,不要和业务代码混在一起
@Service
public class ContentResolverServiceCompsite implements InitializingBean {
private List<ContentResolverService> list = Lists.newArrayList();
@Autowired
private ApplicationContext context;
@Override
public void afterPropertiesSet() throws Exception {
Map<String, ContentResolverService> beansOfType = context.getBeansOfType(ContentResolverService.class);
beansOfType.forEach((k,v)->{
list.add(v);
});
}
public void doResolver(String contentType) {
list.forEach(e->{
if (e.support(contentType))
e.doResolver(NativeRequest.builder().contentType(contentType).build());
});
}
}
这段代码中先把实现了ContentResolverService
接口的类实例初始化到一个list集合中,返回在调用ContentResolverService
接口时循环遍历这个list集合,如果Content-Type
跟自己定义的一样,则调用当前的实现类实例的doResolver
方法。
枚举模式(根据不同的数字返回不同的字符串)
if (1 == request.getCode()) {
return "one";
} else if (2 == request.getCode()) {
return "two";
} else {
return "three";
}
其实,这种判断没有必要,用一个枚举就可以搞定。
@Getter
public enum MessageEnum {
ONE(1,"one"),
TWO(2,"two"),
THREE(3,"three");
private final int code;
private final String message;
MessageEnum(int code, String message) {
this.code = code;
this.message = message;
}
public static MessageEnum getMessageEnum(int code) {
return Arrays.stream(MessageEnum.values())
.filter(e->e.code == code)
.findFirst()
.orElse(null);
}
}
再把调用方法稍微调整一下
public String getMessage(int code) {
return MessageEnum.getMessageEnum(code).getMessage();
}
后续有更好的办法也会持续更新进去…