小额收据原先只显示在屏幕或邮件里,现在审计系统需要 audit/200 的格式。旧方法用渠道名分支,对 audit 抛错。把分支拆成三个类就能叫“策略模式”吗?仅看类图答不了这个问题:先问是谁选择格式、什么行为保持不变、下一次变化要修改哪里。

先补缺失的行为

Before.receipt(200, "audit") 的断言是抛 IllegalArgumentException,刻意保留旧功能缺口。最小修复可以在原 switch 中加一支:Alternative 这样做,还加上负金额拒绝,并保留未知渠道报错。如果渠道是封闭的小集合,需求只来自一个调用位置,这就是合理的实现;没有理由为展示 GoF 模式专门建类层次。

若渠道格式来自调用者的配置,After 接收一个 IntFunction<String>:

1
2
3
After screen = new After(cents -> "paid " + cents);
After audit = new After(cents -> "audit/" + cents);
String result = audit.receipt(200);

协作方向是调用者选择格式函数 → 收据入口检查负金额 → 格式函数生成文本。变化点从 switch 移到组装位置;选择逻辑没有消失,只是换了归属。这里的可插拔函数具有与 Strategy 相似的变化边界,但不等于凡是传 lambda 都实现了 GoF 模式。阅读一个模式应找出意图、稳定流程、参与者的调用关系和变化可能扩散的路径;下一节将用“创建对象由谁决定”的具体问题检验 Factory Method,而不是只比较方法名字。

方案 新增 audit 时 失败行为
Before 旧渠道分支拒绝新增渠道 旧异常由反例断言记录
After 提供新格式函数;调用者负责选择 入口拒绝负数;函数自己的异常不被吞掉
Alternative 添加 audit 分支 拒绝负数与未知渠道

这三个方案只对200 分的收据文本共享旧合同,不声称 Before 对负数也做了校验。若格式还需连接数据库或外部队列,应重新定义失败和重试契约,不能拿这个纯函数例子当生产消息投递保证。

flowchart LR
    Caller[调用方] --> Branch[Before 本地分支]
    Caller --> Context[After]
    Context --> Fn[IntFunction formatter]
    Caller --> Fixed[Alternative 固定输出]

用行为而不是名称验收

在 examples/design-patterns/ 运行 ./mvnw -B -ntp -pl labs/15 -am test 和累计 ./mvnw -B -ntp verify,实际退出码、环境、原始输出见 examples/design-patterns/evidence/15/RUN.md。断言覆盖屏幕和邮件旧文本、新渠道、负金额和未知渠道;既未测 IO 失败也未测渠道运行时加载。

练习:

  1. 新增 sms/200 文本及“负金额不能发出”的需求,先让三个方案分别跑旧渠道合同与新增断言,再记录修改入口、调用者和格式函数的位置。
  2. 将三个格式合并到一个 switch,删掉函数注入,并用测试证明行为仍相同;列出何种外部配置需求出现时再恢复可注入边界。

参考资料

上一节:14 包依赖为什么成环;下一节:16 创建决策何时交给子类。