1. 前言

代码中,如果 if-else 语句比较多,阅读起来比较困难,维护性较差,很容易出bug。接下来,此文将介绍优化 if-else 代码的七种方案:

easyRelus 优化ifElse_User

2. 优化方案

2.1 提前 return,去除不必要的 else

如果 if-else 代码块包含 return 语句,可以考虑通过提前 return,把多余 else 干掉,使代码更加优雅

优化前:

if (condition) {
     // TODO
 } else {
     return;
 }

优化后:

if (!condition) {
     return;
 }
 // TODO

2.2 使用条件三目运算符

使用条件三目运算符可以简化某些 if-else,使代码更加简洁,更具有可读性

优化前:

int status = 0;
if (condition) {
	status = 1;
} else {
	status = 2;
}

优化后:

int satus = condition ? 1 : 2

2.3 使用枚举

在某些时候,使用枚举也可以优化 if-else 逻辑分支(也可以看做一种表驱动方法)

优化前:

String orderStatusDes = "";
if (orderStatus == 0) {
	orderStatusDes = "订单未支付";
} else if (orderStatus == 1) {
	orderStatusDes = "订单已支付";
} else if (orderStatus == 2) {
	orderStatusDes = "已发货";
}

优化后:
定义一个枚举类:

public enum OrderStatusEnum {

    NOT_EXIST(-1, "不存在")
    ,
    NOT_PAID(0, "订单未支付")
    ,
    PAID(1, "订单已支付")
    ,
    SENDED(2, "已发货");

    private Integer code;
    private String message;

    OrderStatusEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

	// 枚举列类中定义一个自定义方法,但如果要想能够被外面访问,需要定义成 static 类型
    public static OrderStatusEnum getOrderStatusEnum(Integer status) {
        for (OrderStatusEnum statusEnum : OrderStatusEnum.values()) {
            if (status.equals(statusEnum.getMessage())) {
                return statusEnum;
            }
        }
        return NOT_EXIST;
    }
}

有了枚举之后,以上 if-else 逻辑分支,可以优化为:

String orderStatusDes = OrderStatusEnum.getOrderStatusEnum(orderStatus).getMessage();

如果一个工程中,定义的枚举类较多,可以实现一个共同的接口

2.4 合并条件表达式

如果有一系列条件返回一样的结果,可以将它们合并为一个条件表达式,让逻辑更加清晰

优化前:

if (age < 18) {
	return 100;
} 
if ("zzc".equals(name)) {
	return 100;
} 
// TODO

优化后:

if (age < 18 || "zzc".equals(name)) {
	return 100;
}
// TODO

2.5 优化逻辑结构,让正常流程走主干

将条件反转使异常情况先退出,让正常流程维持在主干流程,可以让代码结构更加清晰

优化前:

if (captial <= 0.0) {
	return 0.0;
}
if (rate > 0 && duration > 0) {
	return (income / duration) * rate;
}
return 0.0;

优化后:

if (captial <= 0.0) {
	return 0.0;
}
if (rate <= 0 || duration <= 0) {
	return 0.0;
}
return (income / duration) * rate;

2.6 使用 Optional

有时候 if-else 比较多,是因为非空判断导致的,这时候你可以使用 java8 的 Optional 类进行优化

优化前:

if (user != null) {
    if (user.getCity() != null) {
        if (user.getCity().getCode() != null) {
            String value = user.getCity().getCode().getValue();
        }
    }
}

优化后:

Optional.ofNullable(user).map(User::getCity).map(City::getCode).map(Code::getValue).orElse("init");

2.7 策略模式 + 工厂方法消除 if else

根据字段名(年龄、姓名、出生地)不同,将对象集合进行排序(可以升序、降序)

优化前:

// 属性值
String props = "name";
// 升序、降序
String order = "ascending";
List<User> users = new ArrayList<>();  // 将 users 进行赋值

if ("age".equals(props )) {
	if ("ascending".equals(order)) {
		// 升序排序
	} else {
		// 降序排序
	}
} else if ("name".equals(props)) {
	if ("ascending".equals(order)) {
		// 升序排序
	} else {
		// 降序排序
	}
} else if ("birthPlace".equals(props)) {
	if ("ascending".equals(order)) {
		// 升序排序
	} else {
		// 降序排序
	}
}

优化后:

把每个条件逻辑代码块,抽象成一个公共的接口:

public interface PropService {
	// 升序
    void sortAscend(List<User> users);
    // 降序
    void sortDescend(List<User> users);
}

根据每个逻辑条件,定义相对应的策略实现:

public class NamePropServiceImpl implements PropService {

    @Override
    public void sortAscend(List<User> users) {
		Collections.sort(users, Comparator.comparing(User::getName));
    }

    @Override
    public void sortDescend(List<User> users) {
		Collections.sort(users, Comparator.comparing(User::getName).reversed());
    }
}
public class AgePropServiceImpl implements PropService {

    @Override
    public void sortAscend(List<User> users) {
		Collections.sort(users, Comparator.comparing(User::getAge));
    }

    @Override
    public void sortDescend(List<User> users) {
		Collections.sort(users, Comparator.comparing(User::getAge).reversed());
    }
}


public class BirthPlacePropServiceImpl implements PropService {

    @Override
    public void sortAscend(List<User> users) {
		Collections.sort(users, Comparator.comparing(User::getBirthPlace));
    }

    @Override
    public void sortDescend(List<User> users) {
		Collections.sort(users, Comparator.comparing(User::getBirthPlace).reversed());
    }
}

再定义策略工厂类,用来管理这些属性实现策略类,如下:

public class PropServiceFactory {

    private static final Map<String, PropService> propServiceMap = new ConcurrentHashMap<>();

    static {
        propServiceMap.put("name", new NamePropServiceImpl());
        propServiceMap.put("age", new AgePropServiceImpl());
        propServiceMap.put("birthPlace", new BirthPlacePropServiceImpl());
    }

    public static PropService getPropService(String props) {
        return propServiceMap.get(props);
    }
}

使用了策略 + 工厂模式之后,代码变得简洁多了,如下:

public class Test {

    public static void main(String[] args) {
        String props = "name";
        List<User> user = new ArrayList<>();
        
        PropService propService = PropServiceFactory.getPropService(props);
        // 升序
        propService.sortAscend(user);
        // 降序
        propService.sortDescend(user);
    }
}

在工厂类中,我们需要自己手动将属性策略类添加到一个 Map 中去,那么,当我们需要添加一个新的属性名排序时,就需要修改工厂类的源代码了,所以,这里可以进一步改造!!

2.7.1 改造1:使用 Spring

在 Spring 初始化时,需要将策略模式类都注册到工厂类中,将策略类进行改造,使用 Spring 的 InitializingBean 接口,在 bean 初始化后,执行 afterPropertiesSet() 方法进行注册。代码如下:

public class NamePropServiceImpl implements PropService, InitializingBean {

    @Override
    public void sortAscend(List<User> users) {
		Collections.sort(users, Comparator.comparing(User::getName));
    }

    @Override
    public void sortDescend(List<User> users) {
		Collections.sort(users, Comparator.comparing(User::getName).reversed());
    }
	
	@Override
    public void afterPropertiesSet(){
        PropServiceFactory.register("name", this);
    }
}

其它与之相似,这里就省略了。

工厂类稍作改变:

public class PropServiceFactory {

    private static final Map<String, PropService> propServiceMap = new ConcurrentHashMap<>();

    public static void register(String props, PropService propService){
        Assert.notNull(props, "Props can't be null");
        propServiceMap.put(props, PropService);
    }

    public static PropService getPropService(String props) {
        return propServiceMap.get(props);
    }
}

2.7.2 改造2:使用 枚举 + 反射

属性策略实现类都不变

添加一个枚举:

public enum PropServiceEnum {

    NAME_PROP("com.zzc.service.NamePropServiceImpl", "名称")
    ,
    AGE_PROP("com.zzc.service.AgePropServiceImpl", "年龄")
    ,
    BIRTH_PLACE_PROP("com.zzc.service.BirthPlacePropServiceImpl", "出生地")
    ;
	
	// 类的全限定名
    private String propValue;
    private String propName;

    PropServiceEnum(String propValue, String propName) {
        this.propValue = propValue;
        this.propName = propName;
    }

    public String getPropValue() {
        return propValue;
    }

    public String getPropName() {
        return propName;
    }
}

修改工厂类:

public class PropServiceFactory {

    public static PropService getPropService(String props) {
        String className = PropServiceEnum.valueOf(props).getClassName();
        return (PropService) Class.forName(className).newInstance();
    }
}

===========================================================================================
2021-12-09 更:

2.8 Spring + 策略模式 + 自定义注解

优化前:

if ("文本" == msgType) {
	// TODO
} else if ("图片" == msgType) {
	// TODO
} else if ("视频" == msgType) {
	// TODO
} else {
	// TODO
}

根据消息的不同类型有不同的处理策略,如果都放在这种 if else 代码块中,代码很难维护,也很丑。所以,这里就用了策略模式来处理这种情况。

策略模式:就是定义一个接口,然后有多个实现类,每种实现类封装了一种行为。然后根据条件的不同选择不同的实现类。

优化后:

定义一个消息对象:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class MsgVo {
    private Integer type;
    private String content;
}

定义一个消息类型的枚举类:

public enum MsgTypeEnum {

    TEXT(1, "文本")
    ,
    IMAGE(2, "图片")
    ,
    VIDEO(3, "视频")
    ;

    private Integer type;
    private String msg;

    MsgTypeEnum(Integer type, String msg) {
        this.type = type;
        this.msg = msg;
    }

}

定义一个消息处理接口:

public interface MsgService {
    void handler(MsgVo msgVo);
}

处理文本消息实现类:

@Service
public class TextMsgServiceImpl implements MsgService {
    @Override
    public void handler(MsgVo msgVo) {
        System.out.println("处理文本消息:" + msgVo.getContent());
    }
}

处理图片消息实现类:

@Service
public class ImageMsgServiceImpl implements MsgService {
    @Override
    public void handler(MsgVo msgVo) {
        System.out.println("处理图片消息:" + msgVo.getContent());
    }
}

我们也可以使用一个 Map<Integer, MsgService> 来维护消息类型–消息处理对象关系。这样直接根据消息类型就能拿到消息处理对象,调用消息处理对象的方法即可。

但是我们不想手动维护这个 Map 对象,因为每次增加新的消息处理类,Map 的初始化过程就得修改。

这里使用了 自定义注解 + ApplicationListener 来保存这种映射关系:

自定义注解 MsgTypeHandler

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MsgTypeHandler {
    MsgTypeEnum value();
}

然后,给每一个类型添加上这个注解类型:

@Service
@MsgTypeHandler(value = MsgTypeEnum.IMAGE)
public class ImageMsgServiceImpl implements MsgService {
    // ...
}
@Service
@MsgTypeHandler(value = MsgTypeEnum.TEXT)
public class TextMsgServiceImpl implements MsgService {
    // ...
}

用一个 context 对象保存了消息类型->消息处理对象的映射关系:

@Component
public class MsgServiceContext {

    private final Map<Integer, MsgService> msgServiceMap = new HashMap<>();

    public MsgService getMsgService(Integer type) {
        return msgServiceMap.get(type);
    }

    public void putMsgService(Integer type, MsgService msgService) {
        msgServiceMap.put(type, msgService);
    }

}

在 Spring 的启动过程中,通过解析注解,将消息类型->消息处理对象的映射关系保存到MsgServiceContext 对象中:

@Component
public class MsgServiceListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        Map<String, Object> beans = event.getApplicationContext().getBeansWithAnnotation(MsgTypeHandler.class);
        MsgServiceContext msgServiceContext = event.getApplicationContext().getBean(MsgServiceContext.class);
        beans.forEach((name, bean) -> {
            MsgTypeHandler typeHandler = bean.getClass().getAnnotation(MsgTypeHandler.class);
            msgServiceContext.putMsgService(typeHandler.value().getType(), (MsgService) bean);
        });
    }

}

在单元测试中进行测试:

@RunWith(SpringRunner.class)
@SpringBootTest
public class MsgServiceContextTest {

    @Autowired
    private MsgServiceContext msgServiceContext;

    @Test
    public void contextLoads() {
        MsgVo msgVo = new MsgVo(MsgTypeEnum.TEXT.getType(), "消息内容");
        MsgService msgService = msgServiceContext.getMsgService(msgVo.getType());
        msgService.handler(msgVo);
    }

}

===========================================================================================
2022-02-11 更:

2.9 使用函数式接口进行优化

基于 2.8 案例。

在 2.8 案例中,是基于 策略模式 进行优化的。这样的话,有一个明显的缺点:策略实现类会非常多!没法俯视整个分派的业务逻辑。

所以,这次的优化使用了 Map + 函数式 接口来减少类的产生。

Java 8 函数式接口:Java 8 中新增了许多函数式接口。这里,我使用的函数式接口是:Consumer:接受一个输入参数并且无返回的操作

接下来看看如何使用它~~

1、先定义一个消息类型处理器 MsgTypeHandler

@Component
public class MsgTypeHandler {

    @Autowired
    private MsgTypeService msgTypeService;
	// Consumer<String>:要求只有一个入参,且为 String 类型
    private Map<String, Consumer<String>> msgTypeMap = new HashMap<>();

    @PostConstruct
    public void dispatcherInit() {
        msgTypeMap.put("文本", msgType -> msgTypeService.handlerTextMsg(msgType));
        msgTypeMap.put("图片", msgType -> msgTypeService.handlerImageMsg(msgType));
    }

    public void handler(String msgType) {
        Consumer<String> consumer = msgTypeMap.get(msgType);
        if (null != consumer) {
            consumer.accept(msgType);
            return;
        }
        System.out.println("没有需要处理的消息类型~~");
    }

}

上述代码用上了 Java 8 的新特性:lambda 表达式

  • 判断条件放在 key 中
  • 对应的业务逻辑放在 value 中(是一个方法)

这样子写的好处是非常直观,能直接看到判断条件对应的业务逻辑。

2、业务逻辑类 MsgTypeServiceImpl

@Service
public class MsgTypeServiceImpl implements MsgTypeService {

    @Override
    public void handlerTextMsg(String msgType) {
        System.out.println("处理文本消息");
    }

    @Override
    public void handlerImageMsg(String msgType) {
        System.out.println("处理图片消息");
    }

}

3、单元测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class MsgServiceContextTest {
    @Autowired
    private MsgTypeHandler msgTypeHandler;

    @Test
    public void contextLoads2() {
        String msgType = "文本";
        msgTypeHandler.handler(msgType);
    }

}