使用策略模式有哪些常见误区?
简化版
策略模式常见误区包括:为了消灭少量判断而过度设计、把策略选择逻辑散落各处、策略接口抽象不稳定、策略类保存请求状态、把策略模式和状态模式混用、忽略默认策略和异常兜底。
详细版
使用策略模式时要避免这些问题:
- 简单判断也强行拆策略,导致类数量暴涨;
- 策略工厂里继续写复杂业务逻辑,只是换了地方堆分支;
- 策略接口设计太具体,新增策略时频繁改接口;
- 策略对象保存请求级字段,在单例 Bean 场景下引发线程安全问题;
- 调用方直接依赖具体策略类,削弱扩展性;
- 未知类型没有兜底,线上出现空指针或错误执行;
- 分不清策略模式和状态模式、责任链模式的边界。
面试中可以强调:策略模式的价值在于封装可替换算法,不是机械拆类;真正落地时还要设计好策略注册、选择、默认值、监控和测试。
完整版教学
一、误区一:看到 if-else 就上策略模式
策略模式不是清除 if-else 的万能工具。
如果只有两三个简单判断,并且多年不变,直接判断可能更可读。强行抽策略会增加:
- 接口;
- 多个实现类;
- 工厂;
- 注册关系;
- 测试文件。
这些结构本身也有维护成本。只有当分支代表独立业务规则、未来会频繁变化、逻辑比较复杂时,策略模式才更划算。
二、误区二:只是把分支搬到工厂里
有些代码表面上用了策略模式,但工厂里仍然是几百行判断:
if (conditionA && conditionB) {
return strategy1;
}
if (conditionC || conditionD) {
return strategy2;
}
如果选择逻辑确实复杂,可以接受集中管理;但如果工厂开始承载大量业务规则,就要考虑拆分选择器、配置化规则、责任链或规则引擎。
策略工厂应该尽量薄。它的职责是找策略,不是实现业务规则。
三、误区三:策略接口抽象不稳
策略接口决定所有策略的共同契约。接口设计太随意,会造成后续扩展痛苦。
常见坏味道是不断加参数:
calculate(amount, userId, channel, couponId, city, level, scene)
参数越来越多后,很多策略只用其中一两个字段,其他字段都被迫陪跑。
更稳的做法是定义上下文对象:
calculate(DiscountContext context)
同时要保持接口语义清楚,不要让一个策略接口承担多个不同业务动作。
四、误区四:策略类保存请求状态
在 Spring 中,策略类通常是单例 Bean。如果在策略对象里保存某次请求的数据,就会出现并发问题。
错误示例:
private Order currentOrder;
多个线程同时调用时,这个字段会被互相覆盖。
正确做法是策略类尽量无状态,请求数据通过方法参数传入。策略可以持有配置、客户端、DAO 等线程安全依赖,但不要保存单次调用的临时数据。
五、误区五:模式边界混乱
策略模式常和其他模式混淆:
- 和状态模式混淆:策略是算法选择,状态是生命周期流转;
- 和责任链混淆:策略通常选一个执行,责任链通常多个处理器按链路传递;
- 和模板方法混淆:策略用组合替换算法,模板方法用继承固定流程;
- 和工厂模式混淆:工厂负责创建或选择策略,策略负责执行算法。
真实项目里这些模式可以组合使用,但面试时要说清楚各自意图。
六、误区六:忽略兜底、监控和测试
策略模式上线后,最容易出问题的地方往往不是策略类本身,而是选择和兜底:
- 类型拼错找不到策略;
- 新策略没有注册;
- 重复策略覆盖;
- 默认策略使用不当;
- 线上不知道实际命中了哪个策略。
工程上建议:
- 启动时检查重复注册;
- 未知类型明确报错或走业务认可的默认策略;
- 日志记录策略类型;
- 给每个策略写独立单元测试;
- 关键策略加监控指标。
七、用变化频率验证是否值得抽象
若 2 个分支半年不变,引入 2 个接口和注册表收益有限;若每月新增 1 种渠道,半年会到 8 种,策略扩展点价值更高。
identify algorithm family -> stable contract -> stateless implementations -> registry -> tests
这个推演把模式带来的收益和成本放在同一条调用链上。设计评审时既要确认结果正确,也要确认额外层次没有改变原有契约。
八、边界、代价与验证
| 检查维度 | 应确认的内容 |
|---|---|
| 正确性 | 先证明变化轴和可替换性,再决定使用模式,不能从“看到 if”直接跳到“建策略类”。 |
| 适用边界 | 策略接口不要为了迁就所有实现而塞满可空参数;差异过大的规则可能根本不是同一算法族。 |
| 测试证据 | 覆盖正常路径、异常路径、空值或未知类型,并断言调用次数与顺序 |
| 运行成本 | 记录额外对象、调用层数、延迟、线程或内存开销,不用模式名称代替测量 |
模式落地后要用单元测试验证独立职责,用集成测试验证对象装配和真实调用入口。涉及并发、远程或异步时,还要补充竞态、超时、重复执行与资源释放测试。
易错点:先证明变化轴和可替换性,再决定使用模式,不能从“看到 if”直接跳到“建策略类”。
九、常见误区与追问
- 误区:所有策略必须共用一个包含全部字段的万能上下文。 无关字段和大量 null 说明抽象边界可能错误,应拆分策略族或建立明确参数类型。
- 误区:用了策略模式,系统里就不该再出现任何条件判断。 策略消除的是反复扩张的业务算法分支;选择策略、校验输入和兜底仍可能需要有限判断。
- 误区:示例代码能运行,就代表模式边界设计正确。 能运行只证明一条路径,仍要检查异常、并发、顺序、生命周期以及新增实现时是否修改稳定代码。
- 追问:如何测试策略注册是否完整? 除逐个策略单测外,还要验证 key 唯一、未知 key、默认策略和容器启动失败路径。
- 追问:策略对象可以保存本次请求的可变状态吗? 容器单例策略通常不应保存请求状态,应把订单、用户等数据作为参数传入,否则并发调用会相互污染。
- 追问:怎样验证这套设计没有改变原有语义? 先做基线行为测试,再对新增层验证返回值、异常类型、调用次数和顺序;性能敏感处还要比较改造前后的量化指标。
十、加强记忆
策略模式的常见误区可以记成“别滥用、别散选、别乱抽、别有状态、别忘兜底”。它解决的是可替换算法的扩展问题,不是为了把所有判断都拆成类;落地时策略注册、接口设计和异常处理同样重要。