优化代码中大量的if/else语句,你有什么解决方案?
我们在平时开发写的代码中,if-else判断语句基本是必不可少的。当我们的判断语句只有一两层的时候还好,但是过度地、不必要地使用 if...else语句,会对代码的可读性、可扩展性造成负面影响,另外如果判断语句越来越多,后期进行项目维护也会比较困难,对于后面接手项目的人来说,差不多就是一个很头疼的问题了。
所以去除掉代码中过多的if...else语句的能力高低,反映的是程序员对软件重构、设计模式、面向对象设计、架构模式、数据结构等多方面技术的综合运用能力。所以我们的代码中要合理使用 if...else,不能没有设计,也不能过度设计。这些对技术的综合、合理地运用都需要程序员在工作中不断的摸索总结。
这也是该面试题的考察目的!
if (condition1) {
doSomeThing1();
} else if (condition2) {
doSomeThing2();
} else if (condition3) {
doSomeThing3();
} else if (condition4) {
doSomeThing4();
} else {
doSomeThing5();
}...
出现这种情况的原因很多,比如
开发人员变动等。
基本上就是前期迭代懒得优化,来一个需求,加一个if,再来一个需求,再加一个if。久而久之,就成了一座if...else的金字塔。
那么我们如何解决代码中过多的if...else语句呢?可以考虑采用以下解决方案。
适时的return;
策略模式:多态、函数式编程、枚举;
表驱动模式;
职责链模式;
注解驱动;
事件驱动;
有限状态机;
Optional;
Assert;
比如我们有以下的代码:
if (condition) {
// do something
} else {
return xxx;
}
if (!condition) {
return xxx;
}
// do something
我们先判断!condition
,将return语句放在前面,这样就可以去掉else语句了,相对于上面的语句代码清晰了不少。
其实上面的代码完全可以按如下方式优化:
if (!condition) {
return xxx;
}
// do something
我们先判断!condition
,将return语句放在前面,这样就可以去掉else语句了,相对于上面的语句代码清晰了不少。
比如有这么一个开发场景,我们要根据不同的性别执行不同的操作,类似的这种场景是很常见的。 通常的实现如下:
if ("man".equals(strategy)) {
// 执行男人相关操作
} else if ("woman".equals(strategy)) {
// 执行女人相关操作
} else if ("other".equals(strategy)) {
// 执行其他操作
}
对于以上的代码,我们可以采用以下策略进行优化。
2.1 多态的策略优化模式
首先定义一个接口类Strategy。
public interface Strategy {
void run() throws Exception;
}
然后写若干个实现类:
//男人的策略实现类
@Slf4j
public class ManStrategy implements Strategy {
@Override
public void run() throws Exception {
// 快速男人的逻辑
log.debug("执行男人相关的逻辑...");
}
}
//女人的策略实现类
@Slf4j
public class WomanStrategy implements Strategy {
@Override
public void run() throws Exception {
// 快速女人的逻辑
log.debug("执行女人相关的逻辑...");
}
}
//其他人的策略实现类
@Slf4j
public class OtherStrategy implements Strategy {
@Override
public void run() throws Exception {
// 快速其他的逻辑
log.debug("执行其他相关的逻辑...");
}
}
具体使用简单示例:
public class StrategyTest {
public static void main(String[] args) {
try {
Strategy strategy = initMap("man");
strategy.run();
} catch (Exception e) {
e.printStackTrace();
}
}
private static Strategy initMap(String key) {
//使用简单示例
HashMap map = new HashMap<>();
map.put("man", new ManStrategy());
map.put("woman", new WomanStrategy());
map.put("other", new OtherStrategy());
return map.get(key);
}
}
该方案的缺点:
这种优化方案有一个弊端,为了能够快速拿到对应的实现策略,需要一个map对象来保存实习策略,当添加一个新策略的时候,还需要手动添加到这个map中,容易被忽略。
2.2 函数式编程的策略优化模式
我们可以对上面的代码进行稍作修改,采用java8里的函数式编程方法来简化代码。
首先定义一个interface接口。
public interface Strategy {
void run() throws Exception;
}
然后使用函数式编程进行代码的操作。
@Slf4j
public class StrategyTest {
public static void main(String[] args) {
//使用简单示例
HashMap map = new HashMap<>();
map.put("man", () -> log.debug("执行男人相关的逻辑..."));
map.put("woman", () -> log.debug("执行女人相关的逻辑..."));
map.put("other", () -> log.debug("执行其他人相关的逻辑..."));
try {
map.get("woman").run();
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.3 枚举的策略优化模式
定义一个枚举策略类。
@Slf4j
public enum Strategy {
//男人状态
MAN(0) {
@Override
void run() {
//执行男人相关操作
log.debug("执行男人相关的逻辑");
}
},
//女人状态
WOMAN(1) {
@Override
void run() {
//执行女人相关操作
log.debug("执行女人相关的逻辑");
}
},
//其他状态
OTHER(2) {
@Override
void run() {
//执行其他相关操作
log.debug("执行其他相关的逻辑");
}
};
abstract void run();
public int statusCode;
Strategy(int statusCode) {
this.statusCode = statusCode;
}
}
简单使用示例:
public static void main(String[] args) {
try {
//简单使用示例
String param = String.valueOf(Strategy.WOMAN);
Strategy strategy = Strategy.valueOf(param);
strategy.run();
} catch (Exception e) {
e.printStackTrace();
}
}
对于逻辑表达模式固定的 if...else 代码,可以通过某种映射关系,将逻辑表达式用表格的方式表示,也就是从表里查询信息,来找到某个输入所对应的处理逻辑函数,使用这个处理函数进行运算。适用于逻辑表达模式固定的 if...else语句。
例如下面的代码:
if (param.equals(value1)) {
doAction1(someParams);
}else if (param.equals(value2)) {
doAction2(someParams);
}else if (param.equals(value3)) {
doAction3(someParams);
}
可以利用Java 8 的 Lambda 和 Functional Interface来进行重构。
// 这里泛型 ? 是为方便演示,实际可替换为你需要的类型
Map, Function> action> actionMappings = new HashMap<>();
// When init
actionMappings.put(value1, (someParams) -> { doAction1(someParams)});
actionMappings.put(value2, (someParams) -> { doAction2(someParams)});
actionMappings.put(value3, (someParams) -> { doAction3(someParams)});
// 省略 null 判断
actionMappings.get(param).apply(someParams);
当 if...else 中的条件表达式灵活多变,无法将条件中的数据抽象为表格并用统一的方式进行判断时,这时应将对条件的判断权交给每个功能组件。并用链的形式将这些组件串联起来,形成完整的功能。适用于条件表达式灵活多变,没有统一的形式。
职责链的模式在开源框架的 Filter、Interceptor 功能的实现中可以见到很多。下面看一下通用的使用模式:
public void handle(request) {
if (handlerA.canHandle(request)) {
handlerA.handleRequest(request);
} else if (handlerB.canHandle(request)) {
handlerB.handleRequest(request);
} else if (handlerC.canHandle(request)) {
handlerC.handleRequest(request);
}
}
重构代码:
public void handle(request) {
handlerA.handleRequest(request);
}
public abstract class Handler {
protected Handler next;
public abstract void handleRequest(Request request);
public void setNext(Handler next) { this.next = next; }
}
public class HandlerA extends Handler {
public void handleRequest(Request request) {
if (canHandle(request)) doHandle(request);
else if (next != null) next.handleRequest(request);
}
}
通过 Java 注解(或其它语言的类似机制)定义执行某个方法的条件。在程序执行时,通过对比入参与注解中定义的条件是否匹配,再决定是否调用此方法。具体实现时,可以采用表驱动或职责链的方式实现。适用于条件分支很多,对程序扩展性和易用性均有较高要求的场景。通常是某个系统中经常遇到新需求的核心功能。
通过关联不同的事件类型和对应的处理机制,来实现复杂的逻辑,同时达到解耦的目的。从理论角度讲,事件驱动可以看做是表驱动的一种,但从实践角度讲,事件驱动和前面提到的表驱动有多处不同。具体来说:
表驱动通常是一对一的关系;事件驱动通常是一对多;
表驱动中,触发和执行通常是强依赖;事件驱动中,触发和执行是弱依赖
正是上述两者不同,导致了两者适用场景的不同。具体来说,事件驱动可用于如订单支付完成触发库存、物流、积分等功能。
有限状态机通常被称为状态机(无限状态机这个概念可以忽略)。先引用维基百科上的定义:
有限状态机(英语:finite-state machine,缩写:FSM),简称状态机,是表示有限个状态以及在这些状态**之间的转移和动作等行为的数学模型。
其实,状态机也可以看做是表驱动的一种,其实就是当前状态和事件两者组合与处理函数的一种对应关系。当然,处理成功之后还会有一个状态转移处理。
虽然现在互联网后端服务都在强调无状态,但这并不意味着不能使用状态机这种设计。其实,在很多场景中,如协议栈、订单处理等功能中,状态机有这其天然的优势。因为这些场景中天然存在着状态和状态的流转。
Java 代码中的一部分 if...else 是由非空检查导致的。因此,降低这部分带来的 if...else 也就能降低整体的 if...else 的个数。
Java 从 8 开始引入了 Optional 类,用于表示可能为空的对象。这个类提供了很多方法,用于相关的操作,可以用于消除 if...else。开源框架 Guava 和 Scala 语言也提供了类似的功能。适用于有较多用于非空判断的 if...else。
示例代码:
String str = "Hello World!";
if (str != null) {
System.out.println(str);
} else {
System.out.println("Null");
}
利用Optional优化之后:
Optional optional = Optional.of("Hello World!");
optional.ifPresentOrElse(System.out::println, () -> System.out.println("Null"));
上一个方法适用于解决非空检查场景所导致的 if...else,类似的场景还有各种参数验证,比如还有字符串不为空等等。很多框架类库,例如 Spring、Apache Commons 都提供了工具里,用于实现这种通用的功能。这样大家就不必自行编写 if...else 了。适用于通常用于各种参数校验。