← 返回题目列表

策略模式有哪些角色?调用流程是什么?

高频 简单 第 1 / 26 题 更新于 2026/07/28
策略模式StrategyContext调用流程

简化版

策略模式通常有三个角色:策略接口、具体策略、上下文。调用流程是客户端选择或传入策略,上下文持有策略并调用统一方法,真正的算法由具体策略完成。

详细版

策略模式的角色可以这样理解:

  1. Strategy:策略接口,定义所有策略必须实现的方法。
  2. ConcreteStrategy:具体策略,实现某一种算法或业务规则。
  3. Context:上下文,持有策略对象,对外提供业务入口,并把可变行为委托给策略。

调用流程通常是:

  1. 客户端或工厂根据业务条件选择一个策略;
  2. 把策略传给上下文,或者上下文从策略注册表中获取;
  3. 上下文调用策略接口;
  4. 具体策略执行算法并返回结果。

策略模式的关键是“上下文依赖抽象策略接口”,而不是依赖具体策略类。这样新增策略时,上下文不需要知道新策略的内部实现。

完整版教学

一、策略接口负责定义稳定抽象

策略接口是整个模式的中心,它定义的是“所有策略都能做什么”。

以运费计算为例:

public interface FreightStrategy {
    BigDecimal calculate(Order order);
}

这个接口不关心是同城配送、普通快递还是跨境物流,只规定它们都必须能根据订单计算运费。

一个好的策略接口应该满足:

  • 方法含义清晰;
  • 入参尽量稳定;
  • 返回值统一;
  • 不暴露某个具体策略独有的细节。

如果接口为了兼容某个策略不断加参数,说明抽象可能没有切好。

二、具体策略负责封装变化

具体策略承载不同业务规则:

public class LocalFreightStrategy implements FreightStrategy {
    public BigDecimal calculate(Order order) {
        return BigDecimal.valueOf(8);
    }
}

public class ExpressFreightStrategy implements FreightStrategy {
    public BigDecimal calculate(Order order) {
        return order.getWeight().multiply(BigDecimal.valueOf(3));
    }
}

每个策略只处理自己的算法,不关心其他策略。这让代码结构更接近业务语言:看到类名就知道它处理哪种规则。

策略类通常应该保持单一职责。不要把“策略选择逻辑”也写进具体策略里,否则策略之间会互相知道对方存在,耦合又回来了。

三、上下文负责组织调用

上下文对象承接业务入口:

public class FreightContext {
    private final FreightStrategy strategy;

    public FreightContext(FreightStrategy strategy) {
        this.strategy = strategy;
    }

    public BigDecimal calculate(Order order) {
        return strategy.calculate(order);
    }
}

在简单版本中,上下文只持有一个策略对象;在工程版本中,上下文也可能依赖一个策略工厂,由工厂根据订单信息选择策略。

上下文的边界要清楚:它可以做流程编排、参数校验、结果包装,但不要重新写一堆分支去判断具体策略细节。

四、客户端不一定直接 new 策略

教材里常见写法是:

new FreightContext(new ExpressFreightStrategy()).calculate(order);

但真实项目里通常不会到处 new。更常见的是:

  • Spring 注入所有策略;
  • 使用 Map 注册策略;
  • 从配置读取策略类型;
  • 用工厂统一创建或获取策略。

这样可以避免客户端散落大量创建逻辑,也便于统一处理默认策略和异常策略。

五、调用链可以画成一条线

策略模式的调用链可以简化为:

客户端/工厂
  -> 选择具体策略
  -> 上下文 Context
  -> 调用 Strategy 接口
  -> ConcreteStrategy 执行

这条链的重点是:选择和执行被拆开,调用方和算法实现被接口隔离。

六、Strategy、ConcreteStrategy 与 Context 的协作

Context 接收 1 个订单上下文,注册表从 3 个策略中选 1 个执行;策略不需要知道其余两个实现。

client -> Context -> Strategy interface -> ConcreteStrategy

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

七、边界、代价与验证

检查维度应确认的内容
正确性类名不重要,重要的是选择与执行分离,具体策略可在相同契约下替换。
适用边界Context 组织稳定流程,策略负责变化算法;选择逻辑可由客户端、工厂或注册表承担,不应混淆角色。
测试证据覆盖正常路径、异常路径、空值或未知类型,并断言调用次数与顺序
运行成本记录额外对象、调用层数、延迟、线程或内存开销,不用模式名称代替测量

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

易错点:类名不重要,重要的是选择与执行分离,具体策略可在相同契约下替换。

八、常见误区与追问

  • 误区:Context 必须自己 new 所有策略。 它可以通过构造器注入、注册表或容器获得策略,依赖注入通常更易测试。
  • 误区:用了策略模式,系统里就不该再出现任何条件判断。 策略消除的是反复扩张的业务算法分支;选择策略、校验输入和兜底仍可能需要有限判断。
  • 误区:示例代码能运行,就代表模式边界设计正确。 能运行只证明一条路径,仍要检查异常、并发、顺序、生命周期以及新增实现时是否修改稳定代码。
  • 追问:策略能否回调 Context? 技术上可以,但会增强双向耦合;优先通过明确参数和返回值协作。
  • 追问:策略对象可以保存本次请求的可变状态吗? 容器单例策略通常不应保存请求状态,应把订单、用户等数据作为参数传入,否则并发调用会相互污染。
  • 追问:怎样验证这套设计没有改变原有语义? 先做基线行为测试,再对新增层验证返回值、异常类型、调用次数和顺序;性能敏感处还要比较改造前后的量化指标。

九、加强记忆

策略模式的角色记成“接口定规则,策略做实现,上下文负责编排”。调用流程是先选策略,再通过统一接口执行策略;只要上下文不依赖具体实现,新增策略时主流程就能保持稳定。