← 返回题目列表

策略模式中的 Context 应该怎么设计?

高频 中等 第 10 / 26 题 更新于 2026/07/28
策略模式Context上下文对象接口设计

简化版

策略模式中的 Context 负责承载稳定流程和调用策略,不应该包含大量具体策略判断。它可以做参数校验、流程编排、结果包装,也可以持有策略或策略工厂,但具体算法应交给策略实现。

详细版

Context 的设计重点是边界清晰。

它通常可以承担:

  • 接收业务请求;
  • 做通用参数校验;
  • 根据策略工厂获取策略;
  • 调用策略接口;
  • 处理通用日志、异常、结果包装。

它不应该承担:

  • 判断每种策略的内部细节;
  • 把不同策略的业务逻辑写在自己里面;
  • 保存请求级可变状态;
  • 让策略之间互相耦合。

如果 Context 中又出现大量 if-else,说明策略拆分可能不彻底,或者策略选择逻辑应该单独抽成工厂、路由器、规则选择器。

完整版教学

一、Context 的核心定位

策略模式里的 Context 可以理解为“稳定流程的承载者”。它不是具体算法的实现者,而是策略的使用者。

以价格计算为例,Context 可以叫 PriceCalculateService

public class PriceCalculateService {
    private final PriceStrategyFactory factory;

    public PriceResult calculate(PriceRequest request) {
        PriceStrategy strategy = factory.get(request.getPriceType());
        return strategy.calculate(request);
    }
}

这里 Context 负责组织调用,具体价格算法由策略处理。

二、Context 可以持有策略,也可以持有工厂

教材里经常写:

Context context = new Context(strategy);
context.execute();

这种写法适合演示模式本身。

真实项目里更常见的是 Context 持有策略工厂:

PriceStrategy strategy = factory.get(request.getScene());
strategy.calculate(request);

原因是业务请求通常带着类型、渠道、场景等信息,Context 需要在运行时选择策略。

两种写法都可以,重点是不要让 Context 直接依赖大量具体策略类。

三、Context 适合放通用流程

有些逻辑不是具体策略的一部分,而是所有策略都需要的公共流程,这些可以放在 Context。

例如:

  • 请求参数基础校验;
  • 记录策略执行日志;
  • 统计耗时;
  • 捕获并转换异常;
  • 统一包装返回结果;
  • 做权限或幂等校验。

这些逻辑如果放进每个策略,会造成重复。放在 Context 中更合理。

四、Context 不应该重新变成上帝类

策略模式最怕一种写法:表面上抽了策略,Context 里还保留大量业务判断。

例如:

if ("vip".equals(type)) {
    // 一半逻辑写这里
    strategy.calculate(request);
}

这会让职责重新混乱。遇到这种情况,要判断:

  1. 这段判断是不是策略选择?如果是,放到工厂或选择器。
  2. 这段逻辑是不是某个策略独有?如果是,放进具体策略。
  3. 这段逻辑是不是所有策略通用?如果是,留在 Context。

五、上下文参数对象很重要

策略接口的入参最好不要设计成一长串基础类型:

calculate(userId, amount, level, channel, activityId, goodsType)

参数一多,可读性和扩展性都会变差。更好的方式是封装上下文对象:

public class PriceContext {
    private Long userId;
    private BigDecimal amount;
    private String channel;
    private String activityId;
    private List<Item> items;
}

策略接口变成:

PriceResult calculate(PriceContext context);

这样新增字段时不需要频繁改接口签名,也更符合业务语义。

六、Context 保持稳定流程而不过度膨胀

结算 Context 固定执行参数校验、选策略、计算、记录结果 4 步;其中第 3 步算法变化,适合委托策略。

validate -> resolve strategy -> calculate -> audit result

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

七、边界、代价与验证

检查维度应确认的内容
正确性判断 Context 是否过重,要看新增策略是否仍需修改它的核心代码。
适用边界Context 可承载通用编排和不可变上下文,但不应重新出现每种策略的业务分支或保存跨请求状态。
测试证据覆盖正常路径、异常路径、空值或未知类型,并断言调用次数与顺序
运行成本记录额外对象、调用层数、延迟、线程或内存开销,不用模式名称代替测量

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

易错点:判断 Context 是否过重,要看新增策略是否仍需修改它的核心代码。

八、常见误区与追问

  • 误区:Context 越薄越好,最好只剩一行调用。 它可以负责稳定的校验、监控和流程骨架;关键是不要侵入具体算法。
  • 误区:用了策略模式,系统里就不该再出现任何条件判断。 策略消除的是反复扩张的业务算法分支;选择策略、校验输入和兜底仍可能需要有限判断。
  • 误区:示例代码能运行,就代表模式边界设计正确。 能运行只证明一条路径,仍要检查异常、并发、顺序、生命周期以及新增实现时是否修改稳定代码。
  • 追问:请求参数应该拆成很多形参还是上下文对象? 参数较多且共同演进时可用不可变上下文对象,但要避免把无关数据塞成万能 Map。
  • 追问:策略对象可以保存本次请求的可变状态吗? 容器单例策略通常不应保存请求状态,应把订单、用户等数据作为参数传入,否则并发调用会相互污染。
  • 追问:怎样验证这套设计没有改变原有语义? 先做基线行为测试,再对新增层验证返回值、异常类型、调用次数和顺序;性能敏感处还要比较改造前后的量化指标。

九、加强记忆

Context 要记成“稳定流程的组织者”。它可以做通用校验、编排、日志和结果包装,但不要写具体策略逻辑;策略选择交给工厂,算法执行交给策略,复杂入参用上下文对象承载。