Java最新面试题精讲系列02之如何去除代码中过多的if语句

Java最新面试题精讲系列02之如何去除代码中过多的if语句

一. 面试题目

优化代码中大量的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语句呢?可以考虑采用以下解决方案。

  1. 适时的return;

  2. 策略模式:多态、函数式编程、枚举;

  3. 表驱动模式;

  4. 职责链模式;

  5. 注解驱动;

  6. 事件驱动;

  7. 有限状态机;

  8. Optional;

  9. Assert;

1. 适时的return

比如我们有以下的代码: 

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语句了,相对于上面的语句代码清晰了不少。

2. 策略模式

比如有这么一个开发场景,我们要根据不同的性别执行不同的操作,类似的这种场景是很常见的。 通常的实现如下:

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

3. 表驱动模式

对于逻辑表达模式固定的 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 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);

4. 职责链模式

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

5. 注解驱动

通过 Java 注解(或其它语言的类似机制)定义执行某个方法的条件。在程序执行时,通过对比入参与注解中定义的条件是否匹配,再决定是否调用此方法。具体实现时,可以采用表驱动或职责链的方式实现。适用于条件分支很多,对程序扩展性和易用性均有较高要求的场景。通常是某个系统中经常遇到新需求的核心功能。

6. 事件驱动

通过关联不同的事件类型和对应的处理机制,来实现复杂的逻辑,同时达到解耦的目的。从理论角度讲,事件驱动可以看做是表驱动的一种,但从实践角度讲,事件驱动和前面提到的表驱动有多处不同。具体来说:

  1. 表驱动通常是一对一的关系;事件驱动通常是一对多;

  2. 表驱动中,触发和执行通常是强依赖;事件驱动中,触发和执行是弱依赖

正是上述两者不同,导致了两者适用场景的不同。具体来说,事件驱动可用于如订单支付完成触发库存、物流、积分等功能。

7. 有限状态机

有限状态机通常被称为状态机(无限状态机这个概念可以忽略)。先引用维基百科上的定义:

有限状态机(英语:finite-state machine,缩写:FSM),简称状态机,是表示有限状态以及在这些状态**之间的转移和动作等行为的数学模型。

其实,状态机也可以看做是表驱动的一种,其实就是当前状态和事件两者组合与处理函数的一种对应关系。当然,处理成功之后还会有一个状态转移处理。

虽然现在互联网后端服务都在强调无状态,但这并不意味着不能使用状态机这种设计。其实,在很多场景中,如协议栈、订单处理等功能中,状态机有这其天然的优势。因为这些场景中天然存在着状态和状态的流转。

8. Optional

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

9. Assert 模式

上一个方法适用于解决非空检查场景所导致的 if...else,类似的场景还有各种参数验证,比如还有字符串不为空等等。很多框架类库,例如 Spring、Apache Commons 都提供了工具里,用于实现这种通用的功能。这样大家就不必自行编写 if...else 了。适用于通常用于各种参数校验。

 

你可能感兴趣的:(面试题)