上周五晚上十点,我接到一个电话。 Caller ID显示是组里的小李,声音里带着一种程序员特有的绝望。
“哥,实在不行了,这个需求能不能不做?”
我问他什么情况。他说组长让他给订单系统加一个新状态——“待拼单”。听起来简单对吧?但是现有的订单状态机已经是一个写了三年的if-else森林,嵌套层级达到五层,他光是理解现有逻辑就花了两天。
我沉默了三秒钟,然后告诉他:“别动,让我来。”
然后我用策略模式重构了整块代码,两小时搞定,留了充足的测试时间,第二天上线无bug。小李看我的眼神,像是在看一个从未来穿越回来的人。
这个故事告诉我们什么?代码的可维护性不是靠注释堆出来的,是靠设计模式打下来的。当然,也告诉我们组长的代码审美需要拯救。
那些if-else森林,最后都怎么样了
我见过最离谱的订单状态逻辑是这样的:
public String processOrder(Order order) {
if (order != null) {
if (order.getStatus() == 1) {
if (order.getAmount() > 100) {
if (order.getUser().isVip()) {
if (order.getCreateTime().after(threshold)) {
return "给你打折";
} else {
return "不给你打折";
}
} else {
return "不给你打折";
}
} else {
return "不给你打折";
}
} else if (order.getStatus() == 2) {
// ... 又有三层嵌套
}
}
return "参数错误";
}
看到这段代码的那一刻,我深刻理解了为什么有些程序员相信世界上存在魑魅魍魉。因为这种代码本身就是一个诅咒——你读不懂它,它反过来也会读不懂你。
这种嵌套if-else的代码,业界有一个优雅的名字叫“箭头代码”(Arrow Code),因为缩进看起来像一支箭射向屏幕右边。每次我看到这种代码,都会想起一个笑话:为什么程序员喜欢黑暗?因为亮的地方他们看不清那么多缩进。
策略模式是什么,能不能救人
策略模式(Strategy Pattern)大概是Gang of Four里最实用的一员,其他几位兄长——工厂、抽象工厂、单例——在实战中要么被过度使用,要么被误解使用,只有策略模式是我每次重构新项目时第一个想到的工具哥。
策略模式的核心思想很简单:把算法/行为封装成独立对象,使用时不需要知道它的具体实现。这听起来简单到像废话,但我见过太多程序员宁愿写一百行if-else也不愿意写一个接口。
具体怎么用?我拿小李的订单状态问题来做演示。
第一步,定义订单状态处理接口:
public interface OrderStatusHandler {
/**
* 处理订单
* @param order 订单实体
* @return 处理结果
*/
OrderResult handle(Order order);
/**
* 获取该处理器支持的状态
*/
OrderStatus getSupportedStatus();
}
第二步,为每个状态写一个具体策略:
@Component
public class Pending拼单Handler implements OrderStatusHandler {
@Autowired
private OrderService orderService;
@Autowired
private 拼单Service 拼单Service;
@Override
public OrderStatus getSupportedStatus() {
return OrderStatus.PENDING拼单;
}
@Override
public OrderResult handle(Order order) {
// 这里是纯业务逻辑,没有任何if-else
// 如果需要拼单,发起拼单
// 如果拼单超时,走超时逻辑
// 如果用户主动取消,取消拼单
return orderService.发起拼单(order);
}
}
第三步,也是最关键的一步——用一个工厂或者Map来管理这些策略:
@Component
public class OrderStatusHandlerFactory {
private final Map<OrderStatus, OrderStatusHandler> handlerMap;
@Autowired
public OrderStatusHandlerFactory(List<OrderStatusHandler> handlers) {
this.handlerMap = handlers.stream()
.collect(Collectors.toMap(
OrderStatusHandler::getSupportedStatus,
Function.identity(),
(existing, replacement) -> replacement
));
}
public OrderResult process(Order order) {
OrderStatusHandler handler = handlerMap.get(order.getStatus());
if (handler == null) {
throw new IllegalStateException("未找到状态处理器: " + order.getStatus());
}
return handler.handle(order);
}
}
现在,新增一个状态需要做什么?只需要写一个新的Handler实现类,然后在类上加一个@Component注解。Spring会自动发现它,自动注册进工厂。你甚至不需要修改任何现有代码——开闭原则(Open-Closed Principle)在这里被天然满足。
为什么你的同事不愿意改设计模式
这里我要说一些得罪人的话。拒绝设计模式的人,大多数不是不懂设计模式,而是懒得动脑子。他们觉得写if-else够用,反正能跑,反正确认测试过了。
但这里有个经典的工程师认知偏差:把“能跑”等同于“好代码”。 代码能跑和代码写得好是两码事,就像一个人能走路和一个人走得优雅不是一回事。
更可怕的是,if-else森林最阴险的地方在于它的积累效应。每一行if-else看起来都合理,几个嵌套看起来也还能接受。但是当系统运行两三年,加了十几个状态,每个状态又各有三五种分支的时候,这个代码就成了谁都不敢动的结界。
这时候你跟新人说“来,给订单加个‘已退款’状态”,新人的表情大概是这样的:😱
策略模式的几个实战要点
不是所有场景都适合策略模式。我在这里说几个判断标准:
适合的场景:
- 多个地方有类似逻辑但具体细节不同(状态处理、支付渠道、通知方式)
- 需要动态切换算法/行为
- 新增逻辑是常态而不是例外(业务规则频繁变更)
不太适合的场景:
- 逻辑极其简单,最多两个分支,不会再扩展
- 性能敏感到不能接受接口调用开销(但说真的,大多数场景下这不是问题)
另外有一个实战经验:如果一个方法里的if-else超过三层,或者整个方法的代码量超过了80行,你就要警惕了。这不是严谨的衡量标准,但是一个经验值——当代码复杂度达到这个阈值时,维护成本已经开始超过设计成本了。
最后说点真心话
我知道写这篇文章可能会被一些老手吐槽:“设计模式又不是万能药”“过度工程化也是坑”。这些我都同意。设计模式不是银弹,但if-else也不是。
我见过太多项目死于两种极端:一种是从来不重构,if-else堆到天荒地老;另一种是拿到产品需求就开始画类图,恨不得把所有模式都用上,结果一个简单的CRUD写了二十个类。
好的工程判断力在于知道什么时候该用设计模式,什么时候不该用。这不是从书里学来的,是从踩坑里爬出来的。
但有一点我可以打包票:当你面对的是一个复杂的业务状态机,面对的是一个不断加新状态新规则的系统,别犹豫,上策略模式。三个月后你会回来感谢我。六个月后当你轻松加一个新状态而不用在旧代码里穿梭跳跃时,你会想起那个建议你用策略模式的晚上。
那时候给我点个赞就行。
(本文作者是一个被箭头代码伤害过的人,如果你正在经历类似的情况,请考虑给你的代码买一份策略模式保险。)