Java面試題~基于注解+Enum+策略模式優(yōu)化switch case

作者: 修羅debug
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 by-sa 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接和本聲明。



摘要:本文我們將繼續(xù)分享介紹第二道關(guān)于Java面試的“代碼優(yōu)化題”,主要實(shí)現(xiàn)的功能為:基于Spring Boot采用“注解+Enum枚舉+策略模式”的思想優(yōu)化項(xiàng)目中頻繁需要增減if else的判斷或者switch case中常量的取值。

內(nèi)容:最近看到某位正在求職Java后端開(kāi)發(fā)的小伙伴留言給debug發(fā)了這么一道面試題,并咨詢相應(yīng)的解決方案:“在一個(gè)正規(guī)的企業(yè)級(jí)項(xiàng)目中,或多或少會(huì)存在著if else、if else判斷的代碼,倘若判斷的層級(jí)只是2/3層,倒還說(shuō)得過(guò)去,但倘若層級(jí)數(shù)超過(guò)了7/8層,甚至還有的超過(guò)了10層(捂臉),那對(duì)于后來(lái)的維護(hù)者而言或許會(huì)是一場(chǎng)災(zāi)難,而且,如果需要增加、刪減某一層級(jí)的判斷,那難免需要去動(dòng)那段 具有10幾層級(jí)數(shù) 的if else,如果恰巧這段代碼的改動(dòng)很有可能會(huì)影響C端、即用戶端的使用,那將是很糟糕的”

(附注:以上這種情況跟switch case代碼塊堆積體現(xiàn)出來(lái)的效果是一樣的)!

下面,直接看這段代碼吧(talk is sheap,show me the code 還是代碼比較實(shí)在,說(shuō)太多都是廢話):

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));
}
}

從上面這段代碼中可以看出,如果現(xiàn)在需要增加常量值C、D、E、F….以及每個(gè)常量值對(duì)應(yīng)的“方法操作邏輯”,那么毫無(wú)疑問(wèn),需要在execute()方法中新增許許多多的case以及對(duì)應(yīng)的“方法操作邏輯”!

很顯然,這種方式雖然可以實(shí)現(xiàn)功能,但是從“面向?qū)ο笏枷搿币约啊按a簡(jiǎn)潔度”的角度來(lái)看,這顯然是有點(diǎn)冗余以及“面向過(guò)程思想”!

下面,我們從 注解+Enum+策略模式 的角度來(lái)優(yōu)化這段冗余的代碼,我們期望達(dá)到的效果是:可以只用一個(gè)注解的形式來(lái)實(shí)現(xiàn)動(dòng)態(tài)增減上面那些case對(duì)應(yīng)的常量值及其對(duì)應(yīng)的“方法操作邏輯”,其完整的過(guò)程如下所示:

(1)首先,我們需要定義一個(gè)包含上述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,這個(gè)注解將用于“方法操作邏輯實(shí)現(xiàn)類”之上,其源代碼如下所示:  

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

(3)然后,我們將上面那些case常量值對(duì)應(yīng)的“方法操作邏輯”進(jìn)行抽象(設(shè)計(jì)模式之一~策略模式),統(tǒng)一歸結(jié)于下面的接口中:  

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

與此同時(shí),我們定義那些case常量值對(duì)應(yīng)的具體實(shí)現(xiàn)邏輯,即CaseInterface接口的具體實(shí)現(xiàn)類,如case常量值為A的實(shí)現(xiàn)類:  

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

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

case常量值為B的實(shí)現(xiàn)類、case常量值為C的實(shí)現(xiàn)類 在這里我就不貼出來(lái)了,大伙可以自行檢出代碼進(jìn)行查看!

(4)至此,其實(shí)我們已經(jīng)完成了一大半了,但是還有一點(diǎn),即我們?nèi)绾螌⑦@些case常量值(枚舉值) 以及 對(duì)應(yīng)的方法操作邏輯實(shí)現(xiàn)類 一一對(duì)應(yīng)起來(lái)呢!自然而然地,我們想到了采用 Map<String,CaseInterface> 進(jìn)行映射,所以我們需要在項(xiàng)目啟動(dòng)的過(guò)程中,掃描并將上面那些采用了特定的注解CaseAnnotation注解的實(shí)現(xiàn)類 的bean組件 獲取出來(lái)并添加進(jìn)Map中,即完成了 case常量值 ~ 方法操作實(shí)現(xiàn)類 的映射

因此,我們需要手動(dòng)創(chuàng)建一個(gè)用于掃描即將加入spring ioc容器的工具SpringContextUtil,其源代碼如下所示:

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

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

//獲取spring應(yīng)用上下文(容器)-進(jìn)而準(zhǔn)備獲取相應(yīng)的bean
public ApplicationContext getContext(){
return context;
}
}

用于創(chuàng)建上述映射Map的Bean組件InitCaseBeanMapComponent,其完整源代碼如下所示:  

@Component
public class InitCaseBeanMapComponent {

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

@Autowired
private SpringContextUtil springContextUtil;
@PostConstruct
public void init() {
//獲取那些帶上了注解的 處理實(shí)現(xiàn)類
Map<String,Object> map=springContextUtil.getContext().getBeansWithAnnotation(CaseAnnotation.class);
System.out.println("此時(shí)獲取出來(lái)的是:"+map);

//添加 case常量值~方法操作邏輯實(shí)現(xiàn)類 映射
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)最后當(dāng)然是進(jìn)行收尾了,在BaseController開(kāi)發(fā)一方法,用于接收“調(diào)用端-如前端、客戶端等等”發(fā)起請(qǐng)求某個(gè)常量值A(chǔ)、B、C、D…等等時(shí),看看得到的相應(yīng)結(jié)果:  

@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;
}


最后當(dāng)然是發(fā)起相應(yīng)的請(qǐng)求,請(qǐng)求A、B、D并觀察返回的結(jié)果,然后觀察相應(yīng)的響應(yīng)結(jié)果,可以看到結(jié)果當(dāng)然是正確的:  







此時(shí),如果想要新增一個(gè)case常量值以及對(duì)應(yīng)的方法處理邏輯,則不需要?jiǎng)覤aseController的代碼(面向用戶發(fā)起的請(qǐng)求代碼),而只需要添加一個(gè)實(shí)現(xiàn)類以及一個(gè)注解,并將該注解加到該實(shí)現(xiàn)類上面,如下圖所以:  



當(dāng)然啦,從上面改造的整個(gè)過(guò)程來(lái)看,會(huì)發(fā)現(xiàn)一個(gè)蛋疼的事實(shí),“優(yōu)化后的代碼比原先的代碼更多了,哈哈”!這一點(diǎn)確實(shí)是,但是人呢,眼光還是放長(zhǎng)遠(yuǎn)一點(diǎn),當(dāng)層級(jí)數(shù)多的時(shí)候,你會(huì)發(fā)現(xiàn)Controller的代碼或者Service的代碼會(huì)過(guò)千行、過(guò)萬(wàn)行。。。當(dāng)然啦,在這一整體的改造過(guò)程中,也從另外一個(gè)角度證明了一件事情:干大事從來(lái)都得犧牲點(diǎn)什么!  


補(bǔ)充:

1、本文涉及到的相關(guān)的源代碼可以到此地址,check出來(lái)進(jìn)行查看學(xué)習(xí):

https://gitee.com/steadyjack/SpringBootTechnology

2、關(guān)注一下Debug的技術(shù)微信公眾號(hào),最新的技術(shù)文章、課程以及技術(shù)專欄將會(huì)第一時(shí)間在公眾號(hào)發(fā)布哦!