代码优化方案:别再写无限嵌套的if-else了 更加优雅的if-else替代方案

发布时间 2023-08-14 20:47:01作者: KwFruit

1 问题概述

之前写过一个抽奖的业务,在判断客户完成某种类型的活动时候,给客户发放抽奖资格。

代码如下(简化版,并不是真正的业务代码):

    public static void main(String[] args) {
        String type ="";
        if("lc".equals(type)){
            /**
             * 查询数据表 看该客户是否完成理财产品的浏览
             * 然后进行判断发放抽奖资格
             */
        }
        if("lclb".equals(type)){
            /**
             * 查询数据表 看该客户是否完成理财列表的浏览
             * 然后进行判断发放抽奖资格
             */
        }
        if("weix".equals(type)){
            /**
             * 调用统一支付平台接口查询该客户银行卡是否绑定微信
             * 然后判断发放抽奖资格
             * 然后进行判断发放抽奖资格
             */
        }
        if("zfb".equals(type)){
            /**
             * 调用统一支付平台接口查询该客户银行卡是否绑定支付宝
             * 然后判断发放抽奖资格
             * 然后进行判断发放抽奖资格
             */
        }
//当然还有其他的一些 就不全部写出来了 }

这样子的其实还好(但不够优雅),有的业务代码更是嵌套了4层if else ,这样让人阅读起来很困难,看起来不够优雅。

2.解决方案

2.1 尽早返回

又称卫语句,即Guard Statement

WikiPedia:

In computer programming, aguardis abooleanexpressionthat must evaluate to true if the program execution is to continue in the branch in question.

Regardless of which programming language is used, aguard clause,guard code, orguard statement, is a check of integritypreconditionsused to avoid errors during execution. A typical example is checking that a reference about to be processed is not null, which avoids null-pointer failures. Other uses include using a boolean field foridempotence(so subsequent calls are nops), as in thedispose pattern. The guard provides anearly exitfrom asubroutine, and is a commonly used deviation fromstructured programming, removing one level of nesting and resulting in flatter code:[1]replacingif guard { ... }withif not guard: return; ....

实际应用:
if (CollectionUtils.isNotEmpty(list)) {
    // do something
} else {   
    return xxx;
}

使用尽早返回优化:

if (CollectionUtils.isEmpty(list)) {
    return xxx;
}

// do something

可以看到,优化后的代码不仅节省了一个else语句,也能让后续的"do something"节省一层if else包裹,代码看起来更干净一些

2.2 使用switch或三元运算符

if (condition1) {
    doSomeThing1();
} else if (condition2) {
    doSomeThing2();
} else if (condition3) {
    doSomeThing3(); 
} else if (condition4) {
    doSomeThing4();
} else {
    doSomeThing5(); 
}...

 

可以使用switch case语法进行替换

我上面抽奖业务的列子,也可以用这种方案替代,但还是不够优雅

 

或,

 

例如使用三元运算符进行赋值操作:

 

Integer num = obejct == null ? 1 : object.value();

 

2.3 策略模式(重点)

2.3.1 概念

策略模式是一种行为设计模式,即一个对象有一个确定的行为,在不同场景下,这些行为有不同的算法实现。

例如从内蒙通过公共交通去北京是一个确定的行为,在天上这种场景可以选择飞机,地上的场景可以选择火车~

策略模式一般包含三个要素:

  • 抽象策略(Abstract strategy):定义所谓的“确定的行为”,一般由接口或抽象类实现

  • 具体实现(Concrete strategy):封装对应场景下的具体算法实现。

  • 上下文(Context):负责具体实现策略的管理并供对象使用。

2.3.2 使用场景

  • 一个接口或抽象类的各个子类都是为了解决相同的问题,区分这些子类的只有方法实现的不同。

  • 代码中使用大量if else或大面积switch case来选择具体的子实现类

2.3.3 实际应用

例如:

if ("man".equals(strategy)) {   
    // Perform related operations 
} else if ("woman".equals(strategy)) {   
    // Perform operations related to women
} else if ("other".equals(strategy)) {   
    // Perform other operations
}

上面一段代码,每一个if分支完成的都是相同的操作,只是在不同的性别场景下,操作方法的实现不同,那么就可以使用策略模式进行优化:

首先,定义一个抽象策略接口:

public interface Strategy {

    void run() throws Exception;

}

然后,进行不同策略的实现:

//Men's strategy implementation class
@Slf4j
public class ManStrategy implements Strategy {

    @Override
    public void run() throws Exception {
        // Fast man's logic
        log.debug("Execute the logic related to men...");
    }

}

//Women's strategy implementation class
@Slf4j
public class WomanStrategy implements Strategy {

    @Override
    public void run() throws Exception {
        // Fast woman's logic
        log.debug("Execute women related logic...");
    }

}

//Others' policy implementation class
@Slf4j
public class OtherStrategy implements Strategy {

    @Override
    public void run() throws Exception {
        // Fast other logic
        log.debug("Perform other related logic...");
    }

}

最后,进行策略的应用:

public class StrategyTest {

    public static void main(String[] args) {
        try {
            Strategy strategy = initMap("man");
            strategy.run();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //Initialize the Map to obtain a gender policy
    private static Strategy initMap(String key) {
        //Use simple example
        HashMap<String, Strategy> map = new HashMap<>();
        map.put("man", new ManStrategy());
        map.put("woman", new WomanStrategy());
        map.put("other", new OtherStrategy());
        return map.get(key);
    }

}

2.3.4 优劣势分析及优化

2.3.4.1 劣势

整体上来看,使用策略模式虽然剔除了大量的if else语句,但是也引入了更多的类文件,同时在Context中需要维护一个类似注册表的map对象,当增加策略实现时,容易忘记。

优化措施:

在Java中,可以使用函数式编程进行优化:

@Slf4j
public class StrategyTest {

    public static void main(String[] args) {
        //Use simple example
        HashMap<String, Strategy> map = new HashMap<>();
        map.put("man", () -> log.debug("Execute the logic related to men..."));
        map.put("woman", () -> log.debug("Execute women related logic..."));
        map.put("other", () -> log.debug("Execute logic related to others..."));

        try {
            map.get("woman").run();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

或者,使用枚举进行优化:

@Slf4j
public enum Strategy {

    //Man state
    MAN(0) {
        @Override
        void run() {
            //Perform related operations
            log.debug("Execute the logic related to men");
        }
    },
    //Woman state
    WOMAN(1) {
        @Override
        void run() {
            //Perform operations related to women
            log.debug("Execute women related logic");
        }
    },
    //Other status
    OTHER(2) {
        @Override
        void run() {
            //Perform other related operations
            log.debug("Perform other related logic");
        }
    };

    abstract void run();

    public int statusCode;

    Strategy(int statusCode) {
        this.statusCode = statusCode;
    }

}
public static void main(String[] args) {
        try {
            //Simple use example
            String param = String.valueOf(Strategy.WOMAN);
            Strategy strategy = Strategy.valueOf(param);
            strategy.run();
        } catch (Exception e) {
            e.printStackTrace();
        }
}

除此以外,在客户端实际使用策略时,即对象进行方法的调用时,客户端必须知道这个策略的所有实现子类,并需要了解这些子类之间的不同以及各自的应用场景,这样客户端才能选择合适的策略实现“确定的行为”。

2.3.4.2 优势

  • 最直接的好处就是可以让又臭又长的if else代码块看起来更干净。

  • 面向对象的三大特点:封装、继承、多态,在策略模式中都能找到影子。面向接口编程,代码的可扩展性好

  • 代码的可测性好,Mock更方便,减少了分支判断,实现类只需要各自测试即可。

2.3.5 策略模式加工厂模式加函数式编程优化(重点)面试问:你在项目里运用了哪些设计模式,再也不会只说单例模式了。

以我上面的抽奖业务为列子

1 首先,还是定义一个抽象策略接口:

/**
 *  判断完成任务 抽象接口
 */
public interface PerformProcessor {
    Object doneTask(Object data);
}

2 然后在一个类里面利用函数式编程完成不同策略的实现

/**
 * 具体完成任务逻辑类  函数式编程优化
 */
@Slf4j
public class Performs {

    public static PerformProcessor LCLB =(data -> {
        log.debug("完成了浏览理财列表的任务 发放抽奖资格(这里只是模拟业务逻辑,返回值先写空)");
        return null;
    });

    public static PerformProcessor LCCP =(data -> {
        log.debug("完成了浏览理财产品的任务 发放抽奖资格(这里只是模拟业务逻辑,返回值先写空)");
        return null;
    });

    public static PerformProcessor JICP =(data -> {
        log.debug("完成了浏览基金产品的任务 发放抽奖资格(这里只是模拟业务逻辑,返回值先写空)");
        return null;
    });

    public static PerformProcessor JILC =(data -> {
        log.debug("完成了浏览基金产品的任务 发放抽奖资格(这里只是模拟业务逻辑,返回值先写空)");
        return null;
    });

    public static PerformProcessor WXQY =(data -> {
        log.debug("完成了银行卡绑定微信的任务 发放抽奖资格(这里只是模拟业务逻辑,返回值先写空)");
        return null;
    });
    public static PerformProcessor ZFBQY =(data -> {
        log.debug("完成了银行卡绑定支付宝的任务 发放抽奖资格(这里只是模拟业务逻辑,返回值先写空)");
        return null;
    });
}

3 工厂模式注册不同的策略实现

package com.mangoubiubiu.dameifelse;

import java.util.HashMap;
import java.util.Map;

public class PerformManager {

    private static final Map<String, PerformProcessor> processorMap = new HashMap<>();

    /**
     * 构造器私有,防止外部实例化该对象
     */
    private PerformManager(){
    }

    /**
     * 静态代码块 当类加载的时候就注册处理器
     */
    static {
        registerMaskProcessor("LCLB", Performs.LCLB);
        registerMaskProcessor("LCCP",Performs.LCCP);
        registerMaskProcessor("JICP", Performs.JICP);
        registerMaskProcessor("JILC", Performs.JILC);
        registerMaskProcessor("WXQY", Performs.WXQY);
        registerMaskProcessor("ZFBQY", Performs.ZFBQY);

    }

    /**
     * 注册处理器,用户可以注册新的登录理器 或者 覆盖内置的处理器
     *
     * @param type      处理器类型
     * @param processor 登录处理器
     */
    public static void registerMaskProcessor(String type, PerformProcessor processor) {
        processorMap.put(type, processor);
    }


    public static void doneTask(String type,Object param){
        PerformProcessor performProcessor = processorMap.get(type);
        performProcessor.doneTask(param);
    }

}

4 然后测试,传入不同枚举执行了不同方法,无须if else进行判断

 

  public static void main(String[] args) {

        PerformManager.doneTask("LCLB","这是我的入参");



    }

5 当然如果 我LCLB方法想重新写,也可以重新写重新注册,例如

    public static void main(String[] args) {

        PerformManager.doneTask("LCLB","这是我的入参");
        log.debug("----------------------------------------------");
        PerformManager.registerMaskProcessor("LCLB",(data)->{
            log.debug("重写浏览理财的业务逻辑我想打印入参:"+data.toString());
            return null;
        });
        PerformManager.doneTask("LCLB","重构浏览理财业务 这是我的入参 2.0版本");

    }

 

2.4 Optional

if else分支判断的很多情况都是进行非空条件的判断,Optional是Java8开始提供的新特性,使用这个语法特性,也可以减少代码中if else的数量,例如:

优化前:

String str = "Hello World!";

if (str != null) {
    System.out.println(str);
} else {
    System.out.println("Null");
}

优化后:

Optional<String> optional = Optional.of("Hello World!");
optional.ifPresentOrElse(System.out::println, () -> System.out.println("Null"));

2.5 注册表

这种方式和策略模式有相似之处,但注册表更自由,不需要提炼接口,只需要将自定义实现在注册表中注册即可。

例如,优化前:

if (param.equals(value1)) {
    doAction1(someParams);
}else if (param.equals(value2)) {
    doAction2(someParams);
}else if (param.equals(value3)) {
    doAction3(someParams);
}

优化后:

//Generic here? For the convenience of demonstration, it can be replaced with the real type we need in actual development
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)});
 
// Omit null judgment
actionMappings.get(param).apply(someParams);

2.6 责任链模式

先来看一段代码:

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

代码中也是存在一坨if else语句,但是和上述例子不同之处在于,if条件判断权在每个handler组件中,每一个handler的判断方式也可能不尽相同,相当灵活,同一个request可能同时满足多个if条件

解决方案就是参考开源组件中Filter或者Interceptor责任链机制,优化后代码:

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

3 参考

2.3.5 参考Mybatis-flex 源码中脱敏处理方案的写法

其余转载自 https://juejin.cn/post/7239058077273309240