先来一张镇楼图感受一下 if else 的魔法吧。
一、由一个几百行 if 引发的思考
有个场景,50张字典表,需要为其他服务提供一个统一的接口来校验用户输入的字典表 id 是否合法。
校验逻辑已经很清晰了,根据参数选择对应的表校验 id 是否存在。
if("table_a".equals(table)) {
// check id
}
if("table_b".equals(table)) {
// check id
}
if("table_c".equals(table)) {
// check id
}...
再加上参数校验,函数调用,@Autowired bean 等等,一坨几百行的代码 ok 了。再新加表再加 if else 就行了,😋 完美。
如此,N 年后另一个可怜的小伙伴就看到这坨东西。
二、KO 这些 if else
回想上面的场景,实际上就是要根据表名去确定 id 是否存在表中,那么只要将表名与操作对应起来就行了。故而采用哈希表的形式,将表名与操作对应起来。部分代码如下:
// 用于保存表与 Function 的对应关系
private final Map<String, Function<Object, Object>> actionMappings = new ConcurrentHashMap<>(50);
@PostConstruct
private void init() {
// map 初始化
actionMappings.put(TableConstants.TABLE_A, (params) -> tableAManager.getById(params));
}
/**
* 校验逻辑
*
*@param table
*@param id
*/
public boolean valid(String table, Long id) {
Object object = actionMappings.get(table).apply(id);
// 不存在则校验失败
return !Objects.isNull(object);
}
如此,N 多行 if 被消除了,这种编程方式也叫做表驱动。虽然 if 没有了,但是在初始化 actionMappings 的时候还是很多行重复代码。下面采用注解方式解决:
/**
* 标记此注解的 bean 会加入基础数据校验全局 Function Map
*
* @author aysaml
* @date 2020/5/7
*/
@Documented
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidHandler {
TABLE_ENUM value();
}
value 是表名枚举,在需要的类上面加上此注解即可。同时定义一个 context 用来专门存储 actionMappings 。
/**
* 数据校验上下文对象,用于保存各表的 Function Map
*
* @author aysaml
* @date 2020/5/7
*/
@Component
public class CommonDataValidContext {
private static final Logger LOGGER = LoggerFactory.getLogger(CommonDataValidContext.class);
private final Map<String, Function<Object, Object>> actionMappings = new ConcurrentHashMap<>(50);
/**
* 方法加入 mappings
*
* @param model 表名
* @param action 方法
*/
public void putAction(String model, Function<Object, Object> action) {
if (!Objects.isNull(action)) {
actionMappings.put(model, action);
LOGGER.info(
"[{}] add to CommonDataValidContext actionMappings, actionMappings size : {}",
model,
actionMappings.size());
}
}
/**
* 执行方法获取返回结果
*
* @param model
* @param param
* @return
*/
public <P, R> R apply(String model, P param) {
if (actionMappings.containsKey(model)) {
return (R) actionMappings.get(model).apply(param);
} else {
LOGGER.error("执行数据校验时model={}不存在!", model);
throw new RuntimeException("基础数据校验时发生错误:" + model + "表不存在!");
}
}
/**
* 判断 mappings 中是否含有给定 model 的处理方法
*
* @param model
* @return
*/
public boolean containsKey(String model) {
return actionMappings.containsKey(model);
}
/**
* 校验执行方法的返回值是否为空
*
* @param model
* @param param
* @param <P>
* @return
*/
public <P> boolean validResultIsNull(String model, P param) {
return Objects.isNull(this.apply(model, param));
}
}
然后通过监听器的方式,将含有 ValidHandler 注解的方法加入 actionMappings 。
/**
* 基础数据校验处理方法监听器
*
* @author aysaml
* @date 2020/5/7
*/
@Component
public class CommonValidActionListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
Map<String, Object> beans =
event.getApplicationContext().getBeansWithAnnotation(ValidHandler.class);
CommonDataValidContext commonDataValidContext =
event.getApplicationContext().getBean(CommonDataValidContext.class);
beans.forEach(
(name, bean) -> {
ValidHandler validHandler = bean.getClass().getAnnotation(ValidHandler.class);
commonDataValidContext.putAction(
validHandler.value().code(),
(param) -> {
try {
return bean.getClass().getMethod("getById", Long.class).invoke(bean, param);
} catch (Exception e) {
e.printStackTrace();
}
return null;
});
});
}
}
三、更多消除 if else 的方法。
1. 提前return
这样可以使代码在逻辑表达上会更清晰,如下:
if (condition) {
// do something
} else {
return xxx;
}
按照逆向思维来,优化如下:
if (!condition) {
return xxx;
}
// do something
还有一种常见的傻瓜编程(如有冒犯,敬请见谅,对码不对人🙏 ):
if(a > 0) {
return true;
} else {
return false;
}
话不多说了,直接 return a > 0;
不香吗?
2. 策略模式
简单来说就是根据不同的参数执行不同的业务逻辑。
如下:
if (status == 0) {
// 业务逻辑处理 0
} else if (status == 1) {
// 业务逻辑处理 1
} else if (status == 2) {
// 业务逻辑处理 2
} else if (status == 3) {
// 业务逻辑处理 3
}...
优化如下:
- 多态
interface A {
void run() throws Exception;
}
class A0 implements A {
@Override
void run() throws Exception {
// 业务逻辑处理 0
}
}
class A1 implements A {
@Override
void run() throws Exception {
// 业务逻辑处理 1
}
}
// ...
然后策略对象存放在一个 Map 中,如下:
A a = map.get(param);
a.run();
2.2 枚举
public enum Status {
NEW(0) {
@Override
void run() {
//do something
}
},
RUNNABLE(1) {
@Override
void run() {
//do something
}
};
public int statusCode;
abstract void run();
Status(int statusCode){
this.statusCode = statusCode;
}
}
重新定义策略枚举
public enum Aenum {
A_0 {
@Override
void run() {
//do something
}
},
A_1 {
@Override
void run() {
//do something
}
};
//...
abstract void run();
}
通过枚举优化之后的代码如下
Aenum a = Aenum.valueOf(param);
a.run();
3. Java 8 的 Optional
Optional主要用于非空判断,是 Java 8 提供的新特性。
使用之前:
if (user == null) {
//do action 1
} else {
//do action2
}
如果登录用户为空,执行action1,否则执行action 2,使用Optional优化之后,让非空校验更加优雅,间接的减少if操作
Optional<User> userOptional = Optional.ofNullable(user);
userOptional.map(action1).orElse(action2);
4. 决策表
就是上面的表驱动编程方法。
欢迎访问个人博客 获取更多知识分享。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。