14、简化条件逻辑(二):以多态取代条件表达式、引入特例(Null Object模式)、引入断言
条件逻辑这东西,写的时候挺爽,改的时候想哭。我见过不少项目,一个方法里塞了七八个 if-else,每次加新需求都得小心翼翼,生怕碰坏哪条分支。今天咱们聊三个重构手法,专门对付这种「条件爆炸」的场景。
以多态取代条件表达式
说白了,就是让对象自己决定怎么做,而不是你替它做判断。你想想看,如果代码里到处都是「if (type == A) doA() else if (type == B) doB()」,那每次加新类型,你都得改这个判断逻辑。改着改着,就改出 bug 了。
我在项目中遇到过这样一个场景:一个订单系统,根据订单类型计算运费。普通订单、加急订单、国际订单,每种计算方式都不一样。一开始就一个 switch 语句,后来加了五六种类型,那个方法膨胀到两百多行。
重构的思路很简单:把每种订单类型做成一个子类,各自实现运费计算方法。
// 重构前
public double calculateShipping(Order order) {
switch (order.getType()) {
case "NORMAL":
return order.getWeight() * 0.5;
case "EXPRESS":
return order.getWeight() * 1.2 + 10;
case "INTERNATIONAL":
return order.getWeight() * 2.0 + 50;
default:
throw new IllegalArgumentException("未知订单类型");
}
}
// 重构后
public abstract class Order {
public abstract double calculateShipping();
}
public class NormalOrder extends Order {
@Override
public double calculateShipping() {
return getWeight() * 0.5;
}
}
public class ExpressOrder extends Order {
@Override
public double calculateShipping() {
return getWeight() * 1.2 + 10;
}
}
public class InternationalOrder extends Order {
@Override
public double calculateShipping() {
return getWeight() * 2.0 + 50;
}
}
这样改完,加新类型只需要新增一个子类,不用动现有代码。开闭原则,就这么落地了。
引入特例(Null Object模式)
空指针异常,Java 程序员的老朋友了。我记得有一次线上事故,就是因为一个 null 没判断,导致整个支付链路挂了。从那以后,我对 null 特别敏感。
引入特例,说白了就是给「没有值」这种情况一个默认行为。你不需要到处写 if (obj != null),而是让一个空对象自己处理。
// 重构前
public class CustomerService {
public String getCustomerName(Long id) {
Customer customer = findCustomer(id);
if (customer != null) {
return customer.getName();
}
return "未知客户";
}
}
// 重构后
public interface Customer {
String getName();
}
public class RealCustomer implements Customer {
private String name;
@Override
public String getName() {
return name;
}
}
public class NullCustomer implements Customer {
@Override
public String getName() {
return "未知客户";
}
}
public class CustomerService {
public Customer findCustomer(Long id) {
// 如果找不到,返回 NullCustomer 实例
Customer customer = doFind(id);
return customer != null ? customer : new NullCustomer();
}
public String getCustomerName(Long id) {
return findCustomer(id).getName(); // 再也不怕 null 了
}
}
这样做的好处很明显:调用方不用再关心 null 的问题,代码干净多了。不过要注意,Null Object 不能滥用。如果业务上需要区分「没有值」和「有值但为空」,那还是得用 Optional 或者显式 null 判断。
引入断言
断言这东西,很多人觉得可有可无。但我个人习惯,在关键逻辑入口处加断言,能省下大量调试时间。
断言不是用来处理业务异常的,而是用来检查「不应该发生的情况」。比如一个方法要求参数不能为 null,那就在开头断言一下。如果真传了 null,说明调用方有 bug,应该尽早暴露。
public class OrderProcessor {
public void process(Order order) {
// 断言:订单不能为 null,金额必须大于 0
assert order != null : "订单对象不能为 null";
assert order.getAmount() > 0 : "订单金额必须大于 0";
// 正常业务逻辑
// ...
}
}
Java 的 assert 默认是关闭的,需要加 -ea 参数才能启用。所以很多团队更习惯用第三方库的断言工具,或者自己封装一个。
三种手法的对比
| 手法 | 适用场景 | 核心思想 | 注意事项 |
|---|---|---|---|
| 以多态取代条件表达式 | 根据类型做不同行为,且类型可能增加 | 让对象自己决定行为 | 不要过度设计,分支少于三个时没必要 |
| 引入特例(Null Object) | 需要频繁处理 null 或特殊值 | 用默认对象代替 null | 注意区分「没有值」和「值不存在」 |
| 引入断言 | 检查内部假设,尽早暴露 bug | 失败要快,失败要明显 | 不要用断言处理业务逻辑 |
知识体系图
下面这张图展示了三种手法在简化条件逻辑中的位置和关系:
这三种手法,说白了都是在跟「复杂条件」作斗争。多态让类型判断消失,Null Object 让空值判断消失,断言让隐藏假设显式化。用好了,代码的可读性和可维护性都能上一个台阶。
- 多态取代条件:让对象自己决定行为,别替它做判断
- Null Object:用默认对象代替 null,减少防御式判断
- 断言:检查内部假设,失败要快,失败要明显
嗯,今天就聊到这儿。这三种手法在实际项目中很常用,建议你找个机会练练手。代码这东西,光看是学不会的,得动手改。