1. 为什么我们需要优化 if/else
在代码评审会上,我经常看到这样的场景:一个方法里嵌套了七八层if/else,就像俄罗斯套娃一样让人头晕目眩。这种代码不仅难以维护,更可怕的是它会像雪球一样越滚越大——每次新增需求都不得不往这个庞然大物里再塞一个条件分支。
if/else本身并没有错,它是编程语言中最基础的控制结构之一。问题出在我们使用它的方式上。当条件判断超过3层嵌套时,代码的可读性和可维护性就会急剧下降。这时候就该考虑重构了。
我见过最夸张的一个案例:一个电商平台的优惠计算模块,因为不断叠加各种促销规则,最终形成了一个超过20个条件分支的"怪兽"。每次修改都像是在拆炸弹,稍有不慎就会引发线上事故。后来我们花了整整两周时间重构,才把这个定时炸弹拆除。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 策略模式:把条件分支变成对象
2.1 基本实现原理
策略模式的核心思想是将每个条件分支的处理逻辑封装成独立的策略类。举个例子,假设我们要处理不同用户等级的折扣计算:
java复制// 传统if/else写法
public double calculateDiscount(String userLevel) {
if ("VIP".equals(userLevel)) {
return 0.8;
} else if ("Gold".equals(userLevel)) {
return 0.9;
} else {
return 1.0;
}
}
// 策略模式改造后
public interface DiscountStrategy {
double calculate();
}
public class VipDiscount implements DiscountStrategy {
@Override
public double calculate() {
return 0.8;
}
}
// 使用时
Map<String, DiscountStrategy> strategies = new HashMap<>();
strategies.put("VIP", new VipDiscount());
//...
double discount = strategies.get(userLevel).calculate();
2.2 实际应用中的技巧
我在电商项目中实践策略模式时总结了几点经验:
- 使用Spring时,可以结合
@Component注解自动注册策略类:
java复制@Service
public class DiscountStrategyFactory {
@Autowired
private Map<String, DiscountStrategy> strategies;
public DiscountStrategy getStrategy(String type) {
return strategies.get(type);
}
}
- 对于简单的策略,可以考虑使用枚举实现:
java复制public enum DiscountStrategy {
VIP(0.8), GOLD(0.9), NORMAL(1.0);
private double rate;
DiscountStrategy(double rate) {
this.rate = rate;
}
public double calculate() {
return rate;
}
}
注意:当策略逻辑非常复杂或者需要维护状态时,建议使用类实现而非枚举。
3. 状态模式:处理状态流转的利器
3.1 与策略模式的区别
状态模式和策略模式在结构上很相似,但它们的意图不同。策略模式关注的是不同算法的替换,而状态模式关注的是对象内部状态改变带来的行为变化。
以订单状态为例:
java复制public interface OrderState {
void next(Order order);
void prev(Order order);
void printStatus();
}
public class NewOrderState implements OrderState {
@Override
public void next(Order order) {
order.setState(new PaidState());
}
//...
}
public class Order {
private OrderState state;
public void nextState() {
state.next(this);
}
//...
}
3.2 复杂状态管理的实践
在物流系统中,我遇到过这样的需求:一个包裹要经历"已揽收-运输中-派送中-已签收"等多个状态,每个状态下的操作和校验规则都不同。使用状态模式后:
- 将状态转移逻辑封装在各个状态类中
- 使用状态机确保转移的有效性
- 通过Hook方法实现状态变更时的附加操作(如发送通知)
java复制public class ShippedState implements OrderState {
@Override
public void next(Order order) {
if (!order.isPaymentConfirmed()) {
throw new IllegalStateException("未支付订单不能派送");
}
order.setState(new DeliveringState());
notifyDeliveryman(order); // 状态变更的附加操作
}
//...
}
4. 责任链模式:解耦处理流程
4.1 构建处理链条
责任链模式特别适合处理需要经过多个校验或处理步骤的场景。比如审批流程:
java复制public abstract class Approver {
protected Approver next;
public Approver linkWith(Approver next) {
this.next = next;
return next;
}
public abstract boolean approve(int amount);
}
public class Manager extends Approver {
@Override
public boolean approve(int amount) {
if (amount <= 1000) {
System.out.println("经理审批通过");
return true;
}
return next.approve(amount);
}
}
4.2 实际应用中的变体
在Web开发中,我们经常用责任链模式处理拦截器和过滤器。Spring Security的过滤器链就是典型例子。我在实际项目中这样优化过:
- 使用建造者模式简化链的构建:
java复制public class ChainBuilder {
public static Approver buildDefaultChain() {
return new Manager()
.linkWith(new Director())
.linkWith(new CFO());
}
}
- 对于可动态调整的链条,可以引入配置化:
java复制@Bean
public FilterRegistrationBean<MyFilter> myFilter() {
FilterRegistrationBean<MyFilter> reg = new FilterRegistrationBean<>();
reg.setFilter(new MyFilter());
reg.setOrder(1); // 控制责任链顺序
return reg;
}
5. 表驱动法:用数据代替逻辑
5.1 基础实现方式
当条件分支是基于某些固定值的映射时,表驱动法往往是最简洁的方案。比如根据错误码返回错误信息:
java复制// if/else版本
public String getErrorMessage(int code) {
if (code == 404) {
return "Not found";
} else if (code == 500) {
return "Server error";
}
//...
}
// 表驱动版本
private static final Map<Integer, String> ERROR_MESSAGES = Map.of(
404, "Not found",
500, "Server error"
//...
);
public String getErrorMessage(int code) {
return ERROR_MESSAGES.getOrDefault(code, "Unknown error");
}
5.2 高级应用技巧
在游戏开发中,我使用表驱动法管理不同角色的属性:
- 将配置放在JSON/XML中:
json复制{
"warrior": {
"health": 100,
"attack": 20
},
"mage": {
"health": 60,
"attack": 40
}
}
- 结合函数式接口实现行为配置:
java复制Map<String, Runnable> characterActions = new HashMap<>();
characterActions.put("warrior_attack", () -> {
System.out.println("使用剑攻击");
// 攻击逻辑...
});
- 对于复杂逻辑,可以使用命令模式:
java复制interface Command {
void execute();
}
Map<String, Command> commands = new HashMap<>();
commands.put("save", new SaveCommand());
commands.put("load", new LoadCommand());
// 执行
commands.get(commandName).execute();
6. 如何选择合适的设计模式
面对具体场景时,我通常这样决策:
-
策略模式:当不同算法或业务规则需要灵活切换时
- 典型场景:支付方式选择、折扣计算、排序算法
- 优势:符合开闭原则,新增策略不影响现有代码
-
状态模式:当对象行为随内部状态改变而变化时
- 典型场景:订单状态机、游戏角色状态、工作流引擎
- 优势:将状态转移逻辑局部化,避免分散的条件判断
-
责任链模式:当请求需要经过多个处理环节时
- 典型场景:审批流程、过滤器链、异常处理
- 优势:动态调整处理顺序,单一职责原则
-
表驱动法:当输入到输出的映射关系固定时
- 典型场景:错误码处理、配置管理、命令分发
- 优势:简洁直观,易于维护和扩展
在实际项目中,这些模式经常组合使用。比如电商系统中:
- 用策略模式处理不同支付方式
- 用状态模式管理订单生命周期
- 用责任链模式实现优惠券叠加规则
- 用表驱动法配置物流运费
7. 重构现有if/else的步骤
当我接手一个充满if/else的遗留系统时,通常会这样逐步重构:
-
识别并提取条件逻辑
- 使用IDE的"Extract Method"功能将每个分支提取为独立方法
- 暂时保持整体结构不变,只是让代码更清晰
-
分析条件判断的性质
- 是基于类型/状态?→ 考虑状态/策略模式
- 是顺序处理步骤?→ 考虑责任链模式
- 是简单的值映射?→ 考虑表驱动法
-
小范围试验
- 选择一个相对独立的模块进行重构
- 确保有完善的单元测试覆盖
-
逐步替换
- 用新实现创建平行路径
- 通过特性开关控制使用新/旧逻辑
- 验证无误后再完全移除旧代码
-
持续优化
- 观察新模式在实际运行中的表现
- 根据需要进行调整和优化
记得在每次提交时写清重构意图,方便后续维护。我曾经在一个金融项目中,通过6次小步提交完成了一个复杂结算逻辑的重构,每次变更都有明确的目的和完整的测试。
