Java面试题~基于注解+Enum+策略模式优化switch case

作者: 修罗debug
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。



摘要:本文我们将继续分享介绍第二道关于Java面试的“代码优化题”,主要实现的功能为:基于Spring Boot采用“注解+Enum枚举+策略模式”的思想优化项目中频繁需要增减if else的判断或者switch case中常量的取值。

内容:最近看到某位正在求职Java后端开发的小伙伴留言给debug发了这么一道面试题,并咨询相应的解决方案:“在一个正规的企业级项目中,或多或少会存在着if else、if else判断的代码,倘若判断的层级只是2/3层,倒还说得过去,但倘若层级数超过了7/8层,甚至还有的超过了10层(捂脸),那对于后来的维护者而言或许会是一场灾难,而且,如果需要增加、删减某一层级的判断,那难免需要去动那段 具有10几层级数 的if else,如果恰巧这段代码的改动很有可能会影响C端、即用户端的使用,那将是很糟糕的”

(附注:以上这种情况跟switch case代码块堆积体现出来的效果是一样的)!

下面,直接看这段代码吧(talk is sheap,show me the code 还是代码比较实在,说太多都是废话):

public class SwitchCaseExecute {

public static CaseResponse execute(CaseRequest request){
CaseResponse response;
switch (request.getType()){
case "A":
response= methodA(request);
break;
case "B":
response= methodB(request);
break;
//………
default:
response= methodC(request);
break;
}
return response;
}

public static CaseResponse methodA(CaseRequest request){
CaseResponse response=new CaseResponse();
response.setResult("A"+request.getName());
return response;
}

public static CaseResponse methodB(CaseRequest request){
CaseResponse response=new CaseResponse();
response.setResult("B"+request.getName());
return response;
}

public static CaseResponse methodC(CaseRequest request){
CaseResponse response=new CaseResponse();
response.setResult("C"+request.getName());
return response;
}

public static void main(String[] args) {
CaseRequest request=new CaseRequest("A","这是名字");
System.out.println(execute(request));
}
}

从上面这段代码中可以看出,如果现在需要增加常量值C、D、E、F….以及每个常量值对应的“方法操作逻辑”,那么毫无疑问,需要在execute()方法中新增许许多多的case以及对应的“方法操作逻辑”!

很显然,这种方式虽然可以实现功能,但是从“面向对象思想”以及“代码简洁度”的角度来看,这显然是有点冗余以及“面向过程思想”!

下面,我们从 注解+Enum+策略模式 的角度来优化这段冗余的代码,我们期望达到的效果是:可以只用一个注解的形式来实现动态增减上面那些case对应的常量值及其对应的“方法操作逻辑”,其完整的过程如下所示:

(1)首先,我们需要定义一个包含上述case常量值的枚举类CaseEnum,其源代码如下所示:

public enum CaseEnum {
A("A"),
B("B"),
C("C"),
;

private String type;
CaseEnum(String type) {
this.type = type;
}
//省略type的getter setter方法
}

(2)紧接着,我们定义注解CaseAnnotation,这个注解将用于“方法操作逻辑实现类”之上,其源代码如下所示:  

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CaseAnnotation {
CaseEnum value();
}

(3)然后,我们将上面那些case常量值对应的“方法操作逻辑”进行抽象(设计模式之一~策略模式),统一归结于下面的接口中:  

public interface CaseInterface {
String execute(CaseRequest request) throws Exception;
}

与此同时,我们定义那些case常量值对应的具体实现逻辑,即CaseInterface接口的具体实现类,如case常量值为A的实现类:  

@Component
@CaseAnnotation(value = CaseEnum.A)
public class CaseAImpl implements CaseInterface{

@Override
public String execute(CaseRequest request) throws Exception {
return "结果:A -- "+request.getName();
}
}

case常量值为B的实现类、case常量值为C的实现类 在这里我就不贴出来了,大伙可以自行检出代码进行查看!

(4)至此,其实我们已经完成了一大半了,但是还有一点,即我们如何将这些case常量值(枚举值) 以及 对应的方法操作逻辑实现类 一一对应起来呢!自然而然地,我们想到了采用 Map<String,CaseInterface> 进行映射,所以我们需要在项目启动的过程中,扫描并将上面那些采用了特定的注解CaseAnnotation注解的实现类 的bean组件 获取出来并添加进Map中,即完成了 case常量值 ~ 方法操作实现类 的映射

因此,我们需要手动创建一个用于扫描即将加入spring ioc容器的工具SpringContextUtil,其源代码如下所示:

@Component
public class SpringContextUtil implements ApplicationContextAware{
private ApplicationContext context;

@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context=context;
}

//获取spring应用上下文(容器)-进而准备获取相应的bean
public ApplicationContext getContext(){
return context;
}
}

用于创建上述映射Map的Bean组件InitCaseBeanMapComponent,其完整源代码如下所示:  

@Component
public class InitCaseBeanMapComponent {

private static Map<CaseEnum,CaseInterface> processMap=new ConcurrentHashMap<>();

@Autowired
private SpringContextUtil springContextUtil;
@PostConstruct
public void init() {
//获取那些带上了注解的 处理实现类
Map<String,Object> map=springContextUtil.getContext().getBeansWithAnnotation(CaseAnnotation.class);
System.out.println("此时获取出来的是:"+map);

//添加 case常量值~方法操作逻辑实现类 映射
for (Object process:map.values()){
CaseAnnotation annotation=process.getClass().getAnnotation(CaseAnnotation.class);
processMap.put(annotation.value(),(CaseInterface) process);
}
}

public Map<CaseEnum,CaseInterface> getProcessMap(){
return processMap;
}
}

(5)最后当然是进行收尾了,在BaseController开发一方法,用于接收“调用端-如前端、客户端等等”发起请求某个常量值A、B、C、D…等等时,看看得到的相应结果:  

@Autowired
private InitCaseBeanMapComponent mapComponent;

@RequestMapping(value = "/switch/case",method = RequestMethod.GET)
public BaseResponse caseInfo(@RequestParam String type,@RequestParam String name){
BaseResponse response=new BaseResponse(StatusCode.Success);
try {
CaseRequest request=new CaseRequest(type,name);

CaseEnum caseEnum=CaseEnum.valueOf(request.getType());
CaseInterface caseInterface= mapComponent.getProcessMap().get(caseEnum);
response.setData(caseInterface.execute(request));
}catch (Exception e){
response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
}
return response;
}


最后当然是发起相应的请求,请求A、B、D并观察返回的结果,然后观察相应的响应结果,可以看到结果当然是正确的:  







此时,如果想要新增一个case常量值以及对应的方法处理逻辑,则不需要动BaseController的代码(面向用户发起的请求代码),而只需要添加一个实现类以及一个注解,并将该注解加到该实现类上面,如下图所以:  



当然啦,从上面改造的整个过程来看,会发现一个蛋疼的事实,“优化后的代码比原先的代码更多了,哈哈”!这一点确实是,但是人呢,眼光还是放长远一点,当层级数多的时候,你会发现Controller的代码或者Service的代码会过千行、过万行。。。当然啦,在这一整体的改造过程中,也从另外一个角度证明了一件事情:干大事从来都得牺牲点什么!  


补充:

1、本文涉及到的相关的源代码可以到此地址,check出来进行查看学习:

https://gitee.com/steadyjack/SpringBootTechnology

2、关注一下Debug的技术微信公众号,最新的技术文章、课程以及技术专栏将会第一时间在公众号发布哦!