策略模式仅适用于同一行为的多实现、分支≥3、易扩展且逻辑复杂度相近的场景;滥用会导致代码臃肿,应避免为简单分支创建过多策略类。

策略模式该不该用来替换所有 if-else
不是所有 if-else 都值得动用策略模式。只有当条件分支基于**同一行为的不同实现**,且分支数量 ≥ 3、未来可能新增、各分支逻辑复杂度接近时,才真正需要它。否则硬套只会让代码更难读。
常见错误现象:if (type === 'A') { ... } else if (type === 'B') { ... } 里每个分支只有一两行简单赋值或日志,却为此建了 5 个策略类——这是在给维护者添堵。
- 使用场景:支付方式(
AlipayStrategy/WechatPayStrategy/CardPayStrategy)、折扣计算(FullReductionRule/PercentOffRule/BuyOneGetOneRule) - 参数差异:所有策略类必须实现统一接口,比如
execute(input: any): Result,不能一个要context,另一个要config - 性能影响:策略对象通常需提前实例化或懒加载,频繁创建会增加 GC 压力;若分支极少且执行极快,直接 if-else 反而更轻量
如何避免策略注册表变成新 if-else
很多人把策略存进 Map 或对象,再用 strategyMap.get(type) 获取,但初始化这个 map 时又写了一堆 if (type === 'X') map.set('X', new XStrategy()) ——等于把 if-else 搬了个家。
正确做法是让策略“自注册”或靠配置驱动:
- 使用装饰器(TypeScript)或注解(Java),让每个策略类声明自己支持的
type,启动时自动扫描注册 - 用 JSON 配置文件定义 type → class 名映射,运行时动态
require()或反射加载(注意 Node.js 的require.cache和 Webpack 的 tree-shaking 影响) - 最简方案:用 Object literal 直接写死映射,但确保 key 和 value 类型一致,例如
{ 'vip': VipDiscount, 'student': StudentDiscount },别混入字符串或函数
策略类里还能不能再有 if-else
能,但仅限于**当前策略内部的细粒度判断**,比如 StudentDiscount.execute() 里按年级分折扣率:if (grade === 'freshman') { ... }。这不破坏策略模式契约,因为对外仍是单一行为入口。
容易踩的坑:
- 在策略基类或上下文类(Context)里写跨策略的 if-else,比如根据用户地区决定用哪个策略——这说明策略划分维度错了,应该把地区纳入策略选择逻辑,而不是藏在某个策略内部
- 策略类之间互相调用或继承,导致依赖混乱;策略应彼此隔离,只共享输入输出结构
- 把状态(如用户登录态、库存余量)塞进策略构造函数,却不校验是否为空或过期,运行时报
Cannot read property 'xxx' of undefined
TypeScript 下类型安全怎么兜住
光靠 interface 约束 execute() 方法签名不够。如果策略返回类型不同(比如有的返回 Promise<void></void>,有的返回 string),调用方就无法安全处理。
实操建议:
- 定义泛型策略接口:
interface Strategy<tinput toutput> { execute(input: TInput): TOutput }</tinput>,强制每个策略明确自己的输入输出 - 注册表类型写成
Record<string strategy commonoutput>></string>,逼迫所有策略收敛到公共类型,必要时用联合类型或 discriminated union 包装差异字段 - 别忽略
unknown到any的转换风险:从配置读取的 type 字符串必须经过Object.keys(strategyMap).includes(rawType)校验,否则strategyMap[rawType as keyof typeof strategyMap]会绕过类型检查
最常被忽略的一点:策略的生命周期和上下文耦合。比如一个策略依赖 HttpClient 实例,但没做依赖注入或传参隔离,多个请求并发时共享连接池或 token,结果 A 用户看到 B 用户的数据——这不是模式问题,是设计落地时忘了“策略是无状态的”这个前提。










