前言
项目业务代码中有两处case条件超过30多个的switch case语句。第一处的伪代码是下面这样的
public BusinessConfig businessProcess(Integer businessId) {
String businessName = "默认业务名";
BusinessConfig config = businessMapper.getBusinessConfig(businessId);
commonMethod(config)
switch (businessId) {
case 1:
businessName = "业务名1";
break;
case 2:
businessName = "业务名2";
break;
case 3:
businessName = "业务名3";
break;
``````
default: break;
}
config.setBusinessName(businessName);
return config;
}
第二处的伪代码是下面这样的
public BusinessConfig exportBusinessConfig(Integer businessId) {
String businessName = "默认业务名";
BusinessConfig config = businessMapper.getBusinessConfig(businessId);
switch (businessId) {
case 1:
businessName = "业务名1";
method1(configId)
break;
case 2:
businessName = "业务名2";
method2(configId)
break;
case 3:
businessName = "业务名3";
method3(configId)
break;
``````
default: break;
}
config.setBusinessName(businessName);
return config;
}
针对这两种情况,如何优化switch case才能使代码达到更简洁、易维护的目的呐?
业务分析与方案设计
第一种场景,业务有通用逻辑commonMethod(config),不同之处在于通过case语句获取businessId对应的businessName,这种等价于key-value一一对应的键值对关系,因此可以采用map来存储,businessId作为key,businessName作为value。
第二种场景,业务没有通用逻辑,每个case语句除了获取businessName外,还有各自的处理流程,虽然也类似于key-value的形式,但value不是一个自定义的类或者集合,而是方法。针对同一业务,不同方法有不同实现的多变性,有下面三种方式可以解决:
- 将方法抽取为接口方法,声明各个业务的实现类,去实现接口方法,然后businessId作为key,把实现类的实例当做value存入一个map中,使用时根据key取出value,value实例调用接口方法即可。
- 将方法抽取为抽象方法,声明各个业务的子类基础抽象类,去重写抽象方法,然后businessId作为key,把子类的实例当做value存入一个map中,使用时根据key取出value,value实例调用抽象方法即可。
- businessId作为key,函数式接口作为value,存入一个map中,使用时根据key取出value,得到一个函数式接口,调用函数接口的方法传入参数即可。
代码实现
第一种场景
伪代码实现如下
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Service;
@Service
public class BusinessService {
@Resource
private BusinessMapper businessMapper;
public static Map<Integer, String> businessConfigMap = new HashMap<>();
static {
init();
}
public BusinessConfig businessConfig(Integer businessId) {
String businessName = "默认业务名";
BusinessConfig businessConfig = businessMapper.getBusinessConfig(businessId);
commonMethod(businessConfig);
businessName = businessConfigMap.get(businessId);
businessConfig.setBusinessName(businessName);
return businessConfig;
}
public static void commonMethod(BusinessConfig config) {
String businessConfigDetailJson = config.getBusinessConfigDetailJson();
// 做一些业务
}
public static void init() {
for (int i = 0; i < 30; i++) {
int businessId = i + 1;
String businessName = "业务名称" + businessId;
businessConfigMap.put(businessId, businessName);
}
}
}
第二种场景
a方案和b方案会涉及创建多个子类,在这里为了简化,因此不做代码实现了。
c方案,case语句中做了两件事:一个是调用方法,另一个是设置businessName。因此可理解为调用方法并给方法传入一个参数,然后方法返回一个businessName值,符合我们的这个需求的是Function函数式接口。
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.springframework.stereotype.Service;
@Service
public class BusinessService {
@Resource
private BusinessMapper businessMapper;
public Map<Integer, Function<Map<String, Object>, String>> businessConfigMap = new HashMap<>();
public BusinessService() {
init();
}
public BusinessConfig exportBusinessConfigId(Integer businessId) {
String businessName = "默认业务名";
Map<String, Object> map = new HashMap<>();
map.put("key_x", "明天五一节喽!");
map.put("key_y", "祝五一节快乐!");
map.put("key_g", "2024年5月1日!");
map.put("key_h", "五一节假如马上到了!");
BusinessConfig config = businessMapper.getBusinessConfig(businessId);
map.put("config", config);
Function<Map<String, Object>, String> mapStringFunction1 = businessConfigMap.get(businessId);
businessName = mapStringFunction1.apply(map);
Function<Map<String, Object>, String> mapStringFunction2 = businessConfigMap.get(businessId);
businessName = mapStringFunction2.apply(map);
config.setBusinessName(businessName);
return config;
}
public void init() {
int businessId = 1;
String businessName1 = "业务名称" + businessId;
businessConfigMap.put(businessId, function(map -> {
System.out.println(map.get("key_x"));
System.out.println(map.get("key_y"));
BusinessConfig config = (BusinessConfig) map.get("config");
// 对config进行处理1
return businessName1;
}));
businessId = 2;
String businessName2 = "业务名称" + businessId;
businessConfigMap.put(businessId, function(map -> {
System.out.println(map.get("key_g"));
System.out.println(map.get("key_h"));
BusinessConfig config = (BusinessConfig) map.get("config");
// 对config进行处理2
return businessName2;
}));
// 更多业务处理逻辑
}
public Function<Map<String, Object>, String> function(Function<Map<String, Object>, String> function) {
return function;
}
}
总结
- 优化之后的代码,完全去掉了长长的switch case模块代码,只需要从map中根据key取一个对应的值或者函数式接口就可以了。这样的方式大大提高了代码的整洁度、降低了代码维护的难度,遵循了“对扩展开放,对修改关闭”的原则。
- 去掉了switch case模块并不是觉得switch case不好,在case条件少的情况下比if else条件语句强的,但当case条件超过20、30个以上的时候就显得特别的臃肿了,作为一个程序员,怎能看到如此臃肿的代码而置之不理?
- 优化后的代码采用了策略模式的设计思想,这种模式的优势在于当我们需要对业务进行扩展时只需要实现具体的策略,并把新的业务id和新的策略存入map中即可。
- 第二种场景中有a、b、c三个方案,我项目中最终采用的是c方案,因为c方案不用创建很多个类。我的项目中,原有的每个方法体长度大都是几行十几行的,没有必要去创建一个类且类中的方法只有短短的几行十几行,如果创建类,反而增加了类的维护难度。具体怎么选择,需要整理和分析业务场景,最后再决定选择对应的实现方案。