两年前的一个周五下午,我正准备进行代码Review,监控群突然炸了。核心交易系统的CPU水位飙升到90%,排查后发现,罪魁祸首竟是一段长达3000行的PaymentService类。
那是典型的"面条代码":为了支持不同渠道(支付宝、微信、银联)、不同商户类型、不同营销活动的组合,前人堆砌了数不清的if-else嵌套。一位新来的同事只是想加一个小众渠道的立减逻辑,结果因为没看清第18层嵌套的逻辑,导致全渠道金额计算错误。
这不仅仅是代码丑陋的问题,这是架构的隐形债务。对于架构师和高级开发来说,复杂的if-else意味着高昂的认知成本、脆弱的扩展性和随时可能爆炸的线上雷。
很多教程只讲设计模式的UML图,却不讲"什么场景用什么"。今天,我想结合这几年拆解10+线上架构优化的经验,聊聊如何用设计模式真正驯服这些复杂的判断逻辑。
策略模式+Map:消灭"并列式"业务判断
在电商、支付、物流等领域,我们最常见的就是"根据类型A,执行逻辑B"的代码。
痛点场景: 2021年,我接手过一个营销发券系统。产品经理极富创意,每周都要搞新花样:满减券、折扣券、立减券、阶梯券…此时的代码大概长这样:
public void sendCoupon(User user, String type) {
if ("DISCOUNT".equals(type)) {
// 50行折扣券逻辑
} else if ("CASH_OFF".equals(type)) {
// 80行立减券逻辑
} else if ("LADDER".equals(type)) {
// 100行阶梯券逻辑
}
// ...还有十几个else
}
每次上线新券种,都要修改主类,违反了开闭原则(OCP),且极易改坏旧逻辑。
重构方案:策略模式 + Spring Map注入
我们不需要手写工厂类,利用Spring的特性,可以将Bean Name作为Key,服务实例作为Value自动注入。
- 定义接口:
CouponStrategy,包含calculate()和send()方法。 - 具体实现:
DiscountStrategy、CashOffStrategy分别实现接口,并标注@Component("DISCOUNT")。 - Map路由:
@Service
public class CouponService {
// Spring会自动将所有实现类注入到这个Map中
@Autowired
private Map<String, CouponStrategy> strategyMap;
public void sendCoupon(User user, String type) {
CouponStrategy strategy = strategyMap.get(type);
if (strategy == null) {
throw new BusinessException("未知券类型");
}
strategy.send(user);
}
}
实战结果: 重构后,新增一种券类型只需新增一个类,主流程代码零修改。我们在双11大促期间,临时上线了3种新玩法,开发耗时从原来的2天缩短到4小时,且因为代码物理隔离,完全不担心影响核心链路。
责任链模式:驯服"漏斗式"多重校验
另一种让人头秃的场景是复杂的校验逻辑,像漏斗一样层层过滤。
痛点场景: 在一个信贷审批系统中,用户发起借款前需要经过:黑名单校验、地域限制校验、额度校验、风控评分校验、产品状态校验…
原来的代码是这样的:
if (checkBlacklist(user)) {
if (checkLocation(user)) {
if (checkQuota(user)) {
// 业务逻辑...
} else {
return "额度不足";
}
} else {
return "地区不支持";
}
} else {
return "黑名单用户";
}
这种代码被称为"箭头型代码",不仅难读,而且难以调整顺序。如果运营要求"先把地区校验提到黑名单校验之前",开发人员可能要重构半天。
重构方案:责任链模式(Chain of Responsibility)
我们要把每一个校验逻辑封装成一个独立的"处理器(Handler)",然后像搭积木一样把它们串起来。
- 抽象基类:定义
AbstractCheckHandler,包含nextHandler引用和check()方法。 - 链式组装:可以通过数据库配置或Spring Order注解来定义链条的顺序。
public abstract class AbstractCheckHandler {
protected AbstractCheckHandler next;
public void setNext(AbstractCheckHandler next) { this.next = next; }
public Result filter(User user) {
if (!doCheck(user)) {
return fail();
}
if (next != null) {
return next.filter(user);
}
return success();
}
protected abstract boolean doCheck(User user);
}
实战结果: 我们将7个硬编码的校验逻辑拆解为独立的Handler。最爽的是,我们将链条的组装逻辑放到了配置中心(Nacos)。 有一次,风控系统升级,需要临时下线"评分校验",运维人员直接在配置中心把该Handler的开关关掉,整个过程无需重新部署代码,真正实现了业务编排的灵活性。
状态模式:治理"流转式"复杂状态
在订单系统、工单系统或工作流引擎中,实体状态的流转控制往往是Bug的高发区。
痛点场景: 一个电商订单,有新建、支付中、已支付、发货、完成、退款中等状态。每个状态下能做的操作不同,比如"已发货"状态不能直接"取消",“退款中"不能"确认收货”。
如果用if-else判断:
public void cancelOrder(Order order) {
if (order.getStatus() == PAID) {
// 退款逻辑
} else if (order.getStatus() == SHIPPED) {
throw new RuntimeException("已发货不能取消");
} else if (order.getStatus() == CREATED) {
// 直接关闭
}
// ...
}
当状态超过5个,操作超过10个,这个类就会膨胀成上帝类,任何一个状态流转的改动都需要小心翼翼地搜索所有if。
重构方案:状态模式(State Pattern)
将每个状态看作一个独立的类,把该状态下允许的行为封装进去。
- 状态接口:
OrderState,定义pay()、ship()、cancel()等行为。 - 具体状态类:
PaidState、ShippedState。在ShippedState类的cancel()方法中直接抛出异常,而在PaidState中则执行退款逻辑。 - 上下文切换:Context类负责持有当前状态,并委托操作。
实战结果:
我们在重构物流中台的运单状态机时使用了此模式。当时业务方要求增加一个"异常挂起"状态,该状态下拦截所有操作。我们只新增了一个SuspendedState类,重写了所有方法返回"禁止操作"提示,就完成了需求,完全没有触碰核心调度逻辑,测试覆盖率达到了100%。
总结与落地工具
重构不是为了炫技,而是为了降低熵增。
什么时候该重构?我大概总结了一个简单的判断标准:
- 重复度:当一段
if-else逻辑在3个以上的地方重复出现; - 复杂度:当嵌套层级超过3层;
- 易变度:当这段逻辑在过去一个月内被修改了2次以上。
最后,分享一个我团队内部使用的**“重构决策卡”**,你可以复制保存,下次遇到长代码时对照使用:
| 场景特征 | 推荐模式 | 核心收益 |
|---|---|---|
| 同级并列:不同类型执行不同逻辑(如支付渠道、优惠券) | 策略模式 + Map | 这里的else变成了新的Class,符合OCP原则,易扩展 |
| 顺序过滤:多重校验、黑白名单、规则引擎 | 责任链模式 | 逻辑解耦,支持动态编排和热插拔 |
| 状态流转:基于当前状态判断能否执行操作(如订单、审批) | 状态模式 | 消除非法的状态流转,将复杂逻辑分散到各个状态类中 |
| 结构相同细节不同:主流程固定,子步骤有差异(如报表导出) | 模板方法模式 | 复用骨架代码,减少重复开发 |
落地行动指南:
- 不要全盘重写:先挑一个痛点最明显的业务场景(比如改动最频繁的那个Service)。
- 先加测试:在动代码前,必须给现有的
if-else逻辑加上单元测试,这是你的安全网。 - 小步快跑:抽离一个分支 -> 验证 -> 再抽离下一个。不要试图一次性上线完美的架构。
代码整洁度,某种程度上代表了团队的尊严。希望下次你打开IDE时,面对的不再是令人绝望的if-else迷宫,而是清晰优雅的架构森林。