← 返回题目列表

什么是策略模式?它解决什么问题?

高频 简单 第 2 / 26 题 更新于 2026/07/28
策略模式行为型模式算法封装设计模式

简化版

策略模式是把一组可替换的算法或业务规则封装成独立策略类,让调用方通过统一接口使用它们。它主要解决大量 if-else、算法频繁变化、运行时需要切换行为的问题。

详细版

策略模式属于行为型设计模式,核心思想是:把“怎么做”的不同方案抽出来,交给不同策略对象实现;调用方只依赖策略接口,不直接写死具体判断。

典型结构包括:

  1. 策略接口:定义统一行为,比如 calculate()pay()compress()
  2. 具体策略:实现不同算法,比如普通折扣、会员折扣、满减折扣。
  3. 上下文对象:持有策略对象,并在合适时机调用策略。

它适合这些场景:

  • 同一个业务点有多种算法或规则;
  • 规则经常新增、替换、下线;
  • 代码中出现大量按类型分支的 if-elseswitch
  • 需要在运行时根据配置、用户类型、渠道、场景切换行为。

策略模式带来的主要好处是符合开闭原则:新增策略时通常只需要新增一个策略类,并把它注册到选择机制里,不需要反复修改主流程。

完整版教学

一、先抓住策略模式的本质

策略模式不是为了“把代码拆得更碎”,它解决的是“同一个变化点有多种可替换实现”的问题。

比如订单优惠计算:

if (type == NORMAL) {
    return price;
} else if (type == VIP) {
    return price * 0.8;
} else if (type == FULL_REDUCTION) {
    return price - 50;
}

这段代码一开始没问题,但当优惠类型越来越多时,主流程会越来越臃肿。每新增一种优惠,都要改这个判断块,容易影响已有逻辑。

策略模式的做法是把每种优惠计算方式抽成一个策略:

interface DiscountStrategy {
    BigDecimal discount(BigDecimal price);
}

普通优惠、会员优惠、满减优惠分别实现这个接口。订单流程只关心“拿到一个优惠策略并执行”,不关心里面到底怎么计算。

二、策略模式为什么能减少分支

大量分支的根源通常是:一个方法同时承担了“选择规则”和“执行规则”两个职责。

策略模式会把职责拆开:

  • 选择策略:根据类型、配置、用户身份、渠道等找到合适策略;
  • 执行策略:由具体策略对象完成真正业务逻辑。

这样主流程从“判断所有情况”变成“找到策略并执行”:

DiscountStrategy strategy = strategyFactory.get(type);
BigDecimal result = strategy.discount(price);

这不是简单地把 if-else 挪到别处,而是让每个策略拥有独立边界。策略可以单独测试、单独扩展,也更容易被复用。

三、策略模式中的“策略”应该是什么粒度

策略的粒度要围绕一个稳定抽象来切。

如果是支付系统,策略可以是“支付方式”:

  • 支付宝支付策略;
  • 微信支付策略;
  • 银行卡支付策略;
  • 余额支付策略。

如果是物流系统,策略可以是“运费计算规则”:

  • 普通快递;
  • 同城配送;
  • 冷链配送;
  • 跨境配送。

判断粒度是否合适,可以看两个问题:

  1. 这些实现是否能共用同一个接口?
  2. 调用方是否真的只关心结果,而不关心具体算法?

如果答案是肯定的,就很适合抽成策略。

四、策略模式和运行时切换的关系

策略模式的一个关键价值是“运行时切换行为”。也就是说,同一段业务流程可以根据上下文选择不同算法。

例如电商下单时:

用户身份 + 活动类型 + 商品类型 -> 选择优惠策略 -> 计算优惠金额

主流程不必知道每种优惠怎么算,它只需要知道当前订单应该用哪种策略。

如果未来新增“新人券策略”,理想情况下只新增一个策略实现和一条注册关系。订单主流程不需要被迫理解新人券细节。

五、策略模式不等于所有判断都要消灭

面试中容易出现一个误区:以为策略模式就是“不能有 if-else”。

实际上策略模式不是彻底消灭判断,而是把复杂、易变化的业务分支从主流程中隔离出去。选择策略时仍然可能存在判断,只是这个判断应该集中在工厂、注册表、配置中心或依赖注入容器里。

如果只有两三行简单判断,并且未来几乎不会变化,强行上策略模式反而会增加理解成本。

六、稳定接口与可替换算法

订单 200 元:八折策略得 160 元,满 100 减 30 策略得 170 元;输入相同但算法可替换,正是策略边界。

Context(amount=200) -> select key -> Strategy.calculate -> result

这个推演把模式带来的收益和成本放在同一条调用链上。设计评审时既要确认结果正确,也要确认额外层次没有改变原有契约。

七、边界、代价与验证

检查维度应确认的内容
正确性策略封装的是完整规则,不是把 if 分散到多个名字不同但职责模糊的类。
适用边界只有算法族会独立演进、能共享稳定输入输出时才值得抽象;两三个不会增长的简单分支不必模式化。
测试证据覆盖正常路径、异常路径、空值或未知类型,并断言调用次数与顺序
运行成本记录额外对象、调用层数、延迟、线程或内存开销,不用模式名称代替测量

模式落地后要用单元测试验证独立职责,用集成测试验证对象装配和真实调用入口。涉及并发、远程或异步时,还要补充竞态、超时、重复执行与资源释放测试。

易错点:策略封装的是完整规则,不是把 if 分散到多个名字不同但职责模糊的类。

八、常见误区与追问

  • 误区:策略模式要求运行时必须频繁切换。 运行时切换是能力而非硬条件;配置期选择不同实现同样可以使用策略。
  • 误区:用了策略模式,系统里就不该再出现任何条件判断。 策略消除的是反复扩张的业务算法分支;选择策略、校验输入和兜底仍可能需要有限判断。
  • 误区:示例代码能运行,就代表模式边界设计正确。 能运行只证明一条路径,仍要检查异常、并发、顺序、生命周期以及新增实现时是否修改稳定代码。
  • 追问:怎样判断两个分支属于同一策略族? 它们解决同一问题、接受可统一的上下文并返回同类结果,同时实现可相互替换。
  • 追问:策略对象可以保存本次请求的可变状态吗? 容器单例策略通常不应保存请求状态,应把订单、用户等数据作为参数传入,否则并发调用会相互污染。
  • 追问:怎样验证这套设计没有改变原有语义? 先做基线行为测试,再对新增层验证返回值、异常类型、调用次数和顺序;性能敏感处还要比较改造前后的量化指标。

九、加强记忆

策略模式要记成“把多种可替换算法封装起来,让主流程面向统一接口调用”。它适合规则多、变化频繁、需要运行时切换的场景,核心收益是减少主流程分支、隔离变化、方便扩展和测试。