面向接口编程的核心是依赖抽象而非具体实现,优惠券核销组件通过定义couponvalidator、coupondeductor、couponnotifier等稳定接口,按场景、类型、风控等维度解耦,结合策略注册中心与状态机驱动,实现平滑扩展。

面向接口编程的核心是“依赖抽象,而非具体实现”,优惠券核销组件要支持平滑扩展,关键在于把变化点(如核销规则、渠道、状态流转、风控策略)全部抽象为接口,让新增类型无需修改原有代码,只通过实现新接口+配置即可上线。
定义清晰的核销行为契约
所有优惠券核销逻辑必须统一遵循同一套输入输出规范。不要用 if-else 判断券类型来分支执行,而是提取出稳定的行为接口:
- CouponValidator:校验是否可核销(时间、库存、用户资格、使用门槛等),返回 ValidationResult
- CouponDeductor:执行扣减(更新库存、生成核销记录、冻结权益等),返回 DeductResult
- CouponNotifier:通知下游(发消息、调积分、触发营销事件),支持异步
每个接口方法签名保持稳定,比如 validate(CouponContext ctx) 和 deduct(CouponContext ctx),上下文对象 CouponContext 封装订单、用户、券实例、渠道等通用信息,避免接口频繁变动。
按维度解耦,分层插拔式设计
核销流程天然存在多个正交变化维度,每个维度独立建模为接口族,运行时组合:
- 核销场景维度:线上下单、线下扫码、客服代核、批量导入 → 定义 CouponScenario 接口,各场景实现自己的预校验和上下文准备逻辑
- 券类型维度:满减券、折扣券、免单券、阶梯券 → 每种类型实现 CouponStrategy,专注计算实付减免、叠加规则、互斥逻辑
- 风控维度:频率限制、设备指纹、异地校验、黑名单拦截 → 实现 RiskChecker,可链式编排(如 new CompositeRiskChecker(checkers...))
实际执行时,根据券 ID 查元数据,自动装配对应场景 + 类型 + 风控策略组合,不硬编码 switch-case。
用策略注册中心替代硬编码映射
避免在 service 层写 map.put("FULL_DISCOUNT", new FullDiscountStrategy()) 这类静态注册。推荐两种轻量方案:
- 基于 Spring 的 @ConditionalOnProperty 或自定义注解 + 扫描机制,启动时自动收集所有 Strategy 实现类,按 typeCode 注册到内存 registry
- 更灵活的做法:将策略与券模板绑定,数据库 coupon_template 表加 strategy_code 字段(如 "discount_v2"),运行时通过 code 反射或工厂加载对应实现类(配合白名单防注入)
新增一种券类型?只需新增一个类实现 CouponStrategy,加个注解或配个 code,重启或热加载后即生效,老逻辑完全不受影响。
状态机驱动核销生命周期
核销不是原子操作,涉及待核销 → 核销中 → 成功/失败 → 补偿等状态。直接用 if(status==X) status=Y 易出错且难扩展。建议:
- 定义 CouponState 枚举,明确合法状态集合(CREATED, VALIDATING, DEDUCTING, SUCCESS, FAILED, COMPENSATED)
- 定义 StateTransitionRule 接口,封装“什么条件下允许从 A 到 B”,例如:从 VALIDATING → DEDUCTING 需 validator 返回 success
- 核心核销服务只调用 stateMachine.transition(currentState, event),由规则引擎决定是否放行、触发哪段业务逻辑
后续增加“核销后自动开卡”“失败自动重试”等需求,只需新增状态和对应 transition rule,不改主干流程。
不复杂但容易忽略:接口设计初期就要想清楚哪些字段会变(比如未来要支持多币种核销)、哪些属于基础设施(日志、监控、traceId)——这些统一放在 CouponContext 里透传,别让每个实现类重复处理。











