什么是规约模式
规约模式(Specification)把业务规则封装成可命名、可组合(与、或、非)的对象。它既能用于内存校验(给定对象判断是否满足规则),也能翻译成查询条件(拼装 WHERE 子句),从而让规则在多处复用时保持业务语义。埃里克·埃文斯在《领域驱动设计》中把它列为通用模式之一。
基本信息
| 核心能力 | 命名规则、逻辑组合(与或非)、内存校验与查询翻译双用 |
|---|---|
| 适用条件 | 规则需要多处复用、需要组合、或需要在查询中表达同一语义 |
| 主要风险 | 内存判断与数据库查询两套实现不一致,导致结果差异 |
| 落地约束 | 关注可读性与索引友好性,避免规则嵌套过深 |
典型使用场景
- 订单筛选:把「已支付且金额超过阈值且未退款」封装成可复用的规约
- 促销资格判断:规则同时用于下单校验与活动列表筛选
- 权限与合规:把准入条件组合成命名规则,便于审计与说明
- 报表与后台查询:把业务规则翻译为查询条件,避免两处口径不一
- 单元测试:用同一规约做快速内存校验,减少对数据库的依赖
- 规则演进:新增条件只需组合新规约,不改动调用方
组合与双路径使用
组合与双路径使用
# 组合示意
spec = 已支付.and(金额超过(1000)).and(非(已退款))
if spec.is_satisfied_by(order):
允许发货
# 双路径:内存校验与数据库查询共用同一规约定义
spec = 已支付.and(金额超过(1000))
# ORM 侧:翻译为 WHERE 条件(注意字段是否有索引)
# WHERE status = 'paid' AND amount > 1000
# 内存侧:同一规约用于单元测试与快速校验
# 一致性保障:用对照测试锁定两条路径的输出差异两种使用路径对照
| 路径 | 使用方式 | 注意点 |
|---|---|---|
| 内存校验 | 对已加载对象判断是否满足 | 对象字段需完整加载,避免误判 |
| 查询翻译 | 翻译为查询条件交给数据库 | 索引友好性决定性能,避免函数包裹字段 |
| 组合复用 | 把多个规约组合成新规约 | 层数控制,超过三层需拆分命名 |
| 测试锁定 | 两种路径结果一致的对照测试 | 覆盖边界值与空值场景 |
规约的收益在命名:给规则起一个业务名字(如「可发货」)比在代码里散落条件判断更容易理解和评审。
内存校验与查询翻译是两套实现:若不做对照测试,字段语义或空值处理差异会让两处结果悄悄分叉。