设计模式 E01:高阶函数替代了什么
第 28 篇把报价规则抽成 Strategy,第 29 篇把固定步骤放进 Template Method。Java 8 之后,同样的“把算法交给调用者”常被写成 lambda。问题不在 lambda 是否“算模式”,而在它替代的是哪一部分:替代一段可调用算法很容易,替代对象的命名、状态、生命周期和调试边界并不免费。
同一契约下的三种写法
JDK 21 的 IntUnaryOperator 文档把它定义为函数式接口,函数式接口可以作为 lambda 或方法引用的目标;它的抽象方法是 applyAsInt(int)。labs/E01 没有直接依赖这个接口,而是定义了一个领域名更明确的 PriceRule,再用 QuoteService 固定负数检查。这样 lambda 和对象策略都必须通过同一个入口。
1 | |
FunctionalStrategyTest.lambdaAndObjectStrategyShareTheSameContract 断言 101 分都得到 90 分,负数都在入口拒绝。这里的关键不是“lambda 更现代”,而是算法可替换的合同没有变。如果调用者关心的只是“输入金额,输出金额”,lambda 是最短表达;如果规则需要名字、指标标签、开关或可观测状态,命名对象会更清楚。
flowchart LR
Caller[调用者] --> Service[QuoteService]
Service --> Contract[PriceRule.apply]
Contract --> Lambda[lambda: cents -> 9折]
Contract --> Object[LoyaltyRule对象]
Service --> Guard[统一负数检查]
高阶函数能替代 Strategy 中的“算法承载物”,不能自动替代“算法的生命周期边界”。判断是否保留对象,先看调用者是否需要算法以外的信息。
闭包状态也是状态
闭包可以捕获 AtomicInteger,对象策略也可以持有 AtomicInteger。closureStateHasTheSameLifecycleRiskAsObjectState 让两种写法都限制一次使用;第二次报价都会抛 IllegalStateException("discount exhausted")。这说明“函数式”并不等于“无状态”。状态藏在闭包里,比字段更短,但不一定更容易定位。
1 | |
如果这个规则只在一个请求内创建并丢弃,闭包很好;如果它被放进单例、缓存或容器作用域,捕获变量就是共享状态。第 20 篇 Singleton 的教训仍然适用:对象身份和作用域要显式写出来,不能因为语法短就消失。
简单替代方案仍然有效
Alternative 用一个 switch 处理 regular 与 loyalty。只有两个固定规则、选择逻辑只存在一个入口时,这比策略对象更短,也更容易读。新增规则是否应该变成新的 PriceRule,取决于规则是否会被多个入口复用、是否要按配置组合、是否需要独立测试和监控。
| 写法 | 适合场景 | 主要风险 |
|---|---|---|
| lambda | 纯算法、无命名生命周期 | 捕获状态不显眼 |
| 对象策略 | 规则有名字、状态、指标、配置 | 小规则会显得重 |
| 本地分支 | 规则集合封闭且入口单一 | 分支扩散后违反 OCP |
不为语法选择设计。先稳定调用合同,再选择最短的承载方式;承载方式可以从分支演进到 lambda,也可以从 lambda 演进到命名对象。
验证与练习
运行命令:
1 | |
FunctionalStrategyTest 通过 3 个测试;原始日志在 examples/design-patterns/evidence/E01/targeted.stdout.txt。本实验只证明本地报价函数的合同,不证明真实促销配置、并发缓存或货币舍入安全。
- 新增“员工八五折”,先把同一组 0、101、1000 分输入跑过分支、lambda、对象策略,再决定规则选择权属于调用者还是报价入口。
- 把
LimitedUseRule放进一个共享字段,连续两个订单调用。写出失败测试后再决定是每个请求新建规则,还是保留对象并把状态转移到订单上下文。
参考资料:
- Oracle Java SE 21 API:IntUnaryOperator,函数式接口与
applyAsInt(int)。 - 本地实验:
examples/design-patterns/labs/E01/src/test/java/blog/examples/labe01/FunctionalStrategyTest.java。
上一节:39 从变化需求保留必要模式;下一节:E02 record、sealed 类型与模式匹配。






