背景
又到了一周一度的代码审查环节,作为团队里的老咸鱼,我需要对新同事近一周编写的代码进行检查,并指出他们的不足,督促改正。近期同事小明负责研发的功能是根据ID查询同一类对象的详细信息,这些对象有着相似的结构和属性,相同的父类,所以其查询逻辑实现起来也很简单,一个接口就可以搞定,但是小明的代码却总能给我带来惊喜,如下:
@RestController
@RequestMapping("/original")
public class OriginalController {
@Autowired
private ServiceA serviceA;
@Autowired
private ServiceB serviceB;
@Autowired
private ServiceC serviceC;
@Autowired
private ServiceD serviceD;
@Autowired
private ServiceE serviceE;
@GetMapping("/getDataById/{id}")
public BasicInfo getDataById(@RequestParam(value ="type") String type, @PathVariable("id") String id) {
Assert.notEmpty(type, "类型不能为空");
if (type.equals("A")) {
return serviceA.getDataById(id);
}
if (type.equals("B")) {
return serviceB.getDataById(id);
}
if (type.equals("C")) {
return serviceC.getDataById(id);
}
if (type.equals("D")) {
return serviceD.getDataById(id);
}
if (type.equals("E")) {
return serviceE.getDataById(id);
}
return new BasicInfo(id);
}
}
看完代码,我把小明拉过来就是一通猛批:“看看你写的啥玩意儿?谁让你这么写代码的?公司让你们学设计模式,你就是这么学习致用的?赶紧给我改啰!”
工厂模式/策略模式
没过一会儿,小明找我说:“雷哥啊,我改完了,这回我不仅用了设计模式,还用了两种模式实现,你给看看?”
我看了新代码,发现他还真是用了两种模式:“工厂模式”和“策略模式”,代码如下:
// 工厂服务类
@Service
public class FactoryServiceA {
@Autowired
private ServiceA serviceA;
@Autowired
private ServiceB serviceB;
@Autowired
private ServiceC serviceC;
@Autowired
private ServiceD serviceD;
@Autowired
private ServiceE serviceE;
public BasicService<? extends BasicInfo> getServiceByType(String type) {
switch (type) {
case "A" : return serviceA;
case "B" : return serviceB;
case "C" : return serviceC;
case "D" : return serviceD;
case "E" : return serviceE;
}
return null;
}
}
//Controller方法
@Autowired
private FactoryServiceA factoryServiceA;
@GetMapping("/getDataByIdA/{id}")
public BasicInfo getDataByIdA(@RequestParam(value ="type") String type, @PathVariable("id") String id) {
Assert.notEmpty(type, "类型不能为空");
BasicService<? extends BasicInfo> service = factoryServiceA.getServiceByType(type);
if (service != null) {
return service.getDataById(id);
}
return new BasicInfo(id);
}
// 策略模式服务类
@Service
public class StrategyService {
@Autowired
private ApplicationContext applicationContext;
private BasicService<? extends BasicInfo> service;
public void fillServiceByType(String type) {
switch (type) {
case "A":
service = applicationContext.getBean(ServiceA.class);
break;
case "B":
service = applicationContext.getBean(ServiceB.class);
break;
case "C":
service = applicationContext.getBean(ServiceC.class);
break;
case "D":
service = applicationContext.getBean(ServiceD.class);
break;
case "E":
service = applicationContext.getBean(ServiceE.class);
break;
default:
service = null;
break;
}
}
public BasicInfo getDataById(String id) {
if (service != null) {
return service.getDataById(id);
}
return new BasicInfo(id);
}
}
// Controller方法
@Autowired
private StrategyService strategyService;
@GetMapping("/getDataById/{id}")
public BasicInfo getDataById(@RequestParam(value ="type") String type, @PathVariable("id") String id) {
Assert.notEmpty(type, "类型不能为空");
strategyService.setService(type);
return strategyService.getDataById(id);
}
能在短短的半小时内,用两种模式重构原来的功能,说明小明的能力还是挺不错的。我感到很欣慰,毕竟他是在我的指导下才有这番成长的。同时我也有点心虚,我承认我刚才批他的时候声音有点大。
看代码可以发现,在这个方法里,工厂模式和策略模式其实在结构上是比较相似的,区别在于封装的维度,比如把实现方法的每个service作为一个对象封装,通过一个类来选择对应的service,就是工厂模式,如果把service和它的方法作为一个整体来封装,通过一个类来执行对应的方法,就是策略模式。根据迪迷特法则(一个对象应当对其他对象有尽可能少的了解),很明显策略模式更加高明,因为在策略模式里,Controller根本不需要知道BasicService类其及方法。
但是他明明用一种模式实现就行了,为什么要写两种?他是不是在挑衅我?我感觉他就是在挑衅我!
用Map代替Switch
上面的实现方式虽然不错,但也并不是毫无破绽。以我多年有CV经验,没一会就发现了问题,Switch!对,正经人谁用switch啊?这玩意分支多了会增加代码的圈复杂度,case里的条件只能用常量,新手写起来容易漏break,易出错,写起来不优雅,不易扩展,早就被大流淘汰了。于是我准备在新人面前露一手,用Map代替Switch,对工厂服务进行如下调整:
@Service
public class FactoryServiceB {
@Autowired
private ServiceA serviceA;
@Autowired
private ServiceB serviceB;
@Autowired
private ServiceC serviceC;
@Autowired
private ServiceD serviceD;
@Autowired
private ServiceE serviceE;
private final Map<String, BasicService> serviceMap = new HashMap<>();
@PostConstruct
private void fillDataIntoMap() {
serviceMap.put("A", serviceA);
serviceMap.put("B", serviceB);
serviceMap.put("C", serviceC);
serviceMap.put("D", serviceD);
serviceMap.put("E", serviceE);
}
public BasicService getServiceByType(String type) {
return serviceMap.get(type);
}
通过map取service的方式,比case更加简洁,而且有效的解决了扩展难的问题,如果以后需要添加新对象F,直接把service引入,在Map里加一行就行了。
老前辈毕竟还是老前辈啊,就在我沾沾自喜时,部门的胡大佬”恰巧“路过,看了我的代码当场脸色铁青:“这谁写的代码?谁让他这么写的?这种代码不是在丢咱们团队的脸吗?”
我有点方:“大佬,这个代码,有,有什么问题吗?”
胡大佬义正严辞的说:“正经人谁在代码里用魔法值啊,这ABCDE为什么不建个枚举?而且你这代码也太臃肿了,我要是想增加一个对象,扩展起来多不方便?”
工具包的妙用
胡大佬丝毫不给我狡辩的机会,拽过键盘就是一顿输出,分分钟建好了枚举类,还优化了之前的策略模式:
// 枚举类
public enum ServiceTypeEnum {
TYPE_A("A", ServiceA.class),
TYPE_B("B", ServiceB.class),
TYPE_C("C", ServiceC.class),
TYPE_D("D", ServiceD.class),
TYPE_E("E", ServiceE.class),
;
public String getType() {
return type;
}
public Class<? extends BasicService> getServiceClass() {
return serviceClass;
}
private final String type;
private final Class serviceClass;
ServiceTypeEnum(String type, Class serviceClass) {
this.type = type;
this.serviceClass = serviceClass;
}
public static ServiceTypeEnum getEnumByType(String type) {
ServiceTypeEnum[] values = ServiceTypeEnum.values();
for (ServiceTypeEnum en : values) {
if (en.getType().equals(type)) {
return en;
}
}
return null;
}
// 改造后的StrategyService
@Service
public class StrategyServiceB {
private BasicService<? extends BasicInfo> service;
public void setService(String type) {
service = SpringUtil.getBean(ServiceTypeEnum.getEnumByType(type).getServiceClass());
}
public BasicInfo getDataById(String id) {
if (service != null) {
return service.getDataById(id);
}
return new BasicInfo(id);
}
}
// 引入maven依赖
<!-- 工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.12</version>
</dependency>
这个代码论简洁度,规范度完爆了我跟小明的版本,而且扩展起来十分方便,添加新对象时,只需要在枚举类里加一行即可。看完代码,小明情不自禁的献上了自己的膝盖,但是,我不胡。虽然我被碾压,被按在地上磨擦,可我就是不胡!抛开技术不谈,胡大佬难道就没有问题么?
技术的背后
(警告,后面一部分纯属胡说八道了,研发人员千万不要看!)
我承认胡大佬比我强一丢丢,但也只是一丢丢而己,他怎么可能随手一段代码就完爆我?而且我的工位在角落里,他没事跑过来干啥?时间还掐得那么精准?这一切真的是“恰巧”吗?
这么一想,小明也有问题啊!他明明会用设计模式,为什么要把代码写成这样?而且小明与胡大佬的关系也很值得推敲,胡大佬恰巧是小明的导师。
现在看来,这段代码更像是一个设计出来的陷阱,专等着某个傻子踩进去。情势犹如拨云见日,逐渐明朗起来,我有理由怀疑,这是针对我的一次有预谋的狙击。他俩串通好设下这个局,就是想在大庭广众之下用我最擅长的东西狠狠打我的脸。我们什么仇什么怨,他俩要这样害我?
这个问题我一直没找着答案,直到在整理这篇博客时,我忽然发现之前写的一篇博客的阅读数变成了2。这就奇怪了,我写的东西除了我,还有谁会看?凝视着那满是嘲讽意味的阅读数,我想我知道什么原因了,某人肯定已经知道我在博客上黑他的事了。
程序员的世界不仅是简简单单的COPY,还有办公室里波云诡谲,连横合纵的明争暗斗。呵呵呵,事情,开始变得有趣了呢!