策略模式 + 模板方法 + 注册式工厂 统一设计方案(营销优惠场景示例)
文档目的
本档面向开发团队,详细说明一种可复用的架构模式:策略模式 + 模板方法 + 注册式工厂(Dispatcher) ,并在此基础上加入接口能力标签的最佳实践。文档提供:
- 设计理念与目标
- 各类职责说明(到类级别)及为什么要这样设计
- Spring Boot 2.x 实现细节(代码示例)
- 营销优惠(Promotion)示例工程:从代码骨架到运行与测试说明
- 可扩展能力(幂等、异步、重试、限流)的实现策略
- 团队使用规范、命名约定、常见坑
一、设计目标(回顾)
- 高可扩展:新增策略时不修改核心框架代码
- 低耦合:调用方只依赖接口(IHandler)而非实现
- 统一流程:模板方法负责统一流程(前置、处理、后置)
- 自动注册:Dispatcher 自动发现并注册 Handler,避免硬编码
- 可贴标签扩展能力:通过额外接口(如 AsyncHandler、Idempotent)实现横切能力
二、总体架构与类职责
2.1 核心组件概览
-
IHandler(接口)
- 声明契约:
String getCode()、void handle(Context ctx) - 作用:面向接口编程,外部通过接口依赖,不直接依赖抽象类实现
- 声明契约:
-
AbstractHandler(抽象类 / 模板方法)
- 提供模板方法
final void handle(Context),内部调用before()、doHandle()、after() - 可注入公共服务(例如:log、metrics、service)
- 作用:复用通用逻辑,规范流程顺序
- 提供模板方法
-
ConcreteHandler(具体实现类)
- 实现
getCode()和doHandle(Context) - 仅包含业务逻辑(例如:计算优惠、发券、记录统计)
- 实现
-
HandlerDispatcher(注册式工厂 / 路由)
- Spring 注入
List<IHandler>,构建Map<String, IHandler>注册表 - 提供
dispatch(code, context)方法,运行对应 handler - 处理找不到 handler 的兜底策略(抛错、降级)
- Spring 注入
-
能力标签接口(可选)
- 示例:
AsyncHandler、IdempotentHandler、RetryableHandler、RateLimited等 - 作用:通过
instanceof或 Spring AOP 自动启用相应横切逻辑
- 示例:
-
Context(数据载体)
- 统一使用 DTO 或 Context 对象传递数据,不直接传 raw JSON
- 方便扩展字段(traceId、bizId、version)
2.2 为什么需要这些类(逐条解释)
-
接口 IHandler:
- 原因:面向接口编程,利于单元测试、Mock、SPI 扩展。接口定义是稳定对外合约,扩展新实现不会破坏依赖者。
-
AbstractHandler:
- 原因:模板方法能把通用的前置/后置逻辑(审计、幂等校验、埋点、异常兜底)统一封装,避免每个 Handler 都写重复代码。
-
ConcreteHandler:
- 原因:业务聚焦,开发人员只需实现
doHandle,降低出错概率与学习成本。
- 原因:业务聚焦,开发人员只需实现
-
HandlerDispatcher:
- 原因:集中管理 handler,避免 if/else、switch、反射动态查找的复杂性,支持运行时扩展。
-
能力标签接口:
- 原因:有些特性(异步、限流、幂等、重试)是横切关注点。通过接口可在框架层面检测并施加策略,而不修改业务代码。
三、代码示例(营销优惠 Promotion 场景)
3.1 场景说明
业务:系统接收营销活动触发请求(例如基于事件或用户操作),根据不同活动类型(满减、折扣、折扣券、买赠、阶梯优惠等)执行不同算法,输出最终优惠结果并发放优惠券或产生订单折扣。
目标:使用本设计模式实现营销优惠引擎,便于扩展新活动类型,同时支持:幂等、异步、重试能力。
3.2 项目结构(建议)
promotion-demo/ (maven)
├─ promotion-api/ (公共 DTO、Context)
├─ promotion-core/ (抽象类、Dispatcher、能力接口)
├─ promotion-handlers/ (具体 Handler 实现)
└─ promotion-app/ (Spring Boot 启动器、演示 Controller/CLI)
3.3 关键代码(精简可运行样例)
3.3.1 Context 与 DTO
java
// promotion-api/src/main/java/com/example/promotion/PromotionContext.java
public class PromotionContext {
private String code; // handler code
private String userId;
private String orderId;
private BigDecimal amount;
private Map<String, Object> extra = new HashMap<>();
// getters & setters
}
3.3.2 IHandler 接口
java
// promotion-core
public interface IHandler {
String getCode();
void handle(PromotionContext ctx) throws Exception;
}
3.3.3 抽象模板 AbstractHandler
java
@Slf4j
public abstract class AbstractHandler implements IHandler {
@Autowired
protected PromotionLogService promotionLogService; // 示例公共服务
@Override
public final void handle(PromotionContext ctx) throws Exception{
String trace = startTrace(ctx);
try{
preHandle(ctx);
doHandle(ctx);
postHandle(ctx);
} catch (Exception e){
onError(ctx,e);
throw e;
} finally{
endTrace(trace);
}
}
protected String startTrace(PromotionContext ctx){
// 例如生成traceId,埋点
return UUID.randomUUID().toString();
}
protected void preHandle(PromotionContext ctx){
// 通用校验 (幂等、权限、参数)
log.info("preHandle {}", getCode());
}
protected abstract void doHandle(PromotionContext ctx) throws Exception;
protected void postHandle(PromotionContext ctx){
// 通用后置(日志、埋点、版本)
log.info("postHandle {}", getCode());
}
protected void onError(PromotionContext ctx, Exception e){
promotionLogService.logError(ctx, e);
}
}
3.3.4 能力接口(示例)
java
public interface AsyncHandler {}
public interface IdempotentHandler {}
public interface RetryableHandler {}
public interface RateLimitedHandler {}
3.3.5 具体 Handler 示例(满减)
java
@Component
public class ThresholdDiscountHandler extends AbstractHandler {
@Override
public String getCode(){ return "THRESHOLD_DISCOUNT"; }
@Override
protected void doHandle(PromotionContext ctx){
BigDecimal amount = ctx.getAmount();
// 简单规则:满 100 减 10
if(amount.compareTo(new BigDecimal(100)) >= 0){
ctx.getExtra().put("discount", new BigDecimal(10));
} else {
ctx.getExtra().put("discount", BigDecimal.ZERO);
}
}
}
3.3.6 注册式工厂/Dispatcher
java
@Component
public class HandlerDispatcher {
private final Map<String, IHandler> handlerMap;
@Autowired
public HandlerDispatcher(List<IHandler> handlers){
this.handlerMap = handlers.stream()
.collect(Collectors.toMap(IHandler::getCode, h->h));
}
public void dispatch(String code, PromotionContext ctx) throws Exception{
IHandler h = handlerMap.get(code);
if(h == null) throw new IllegalArgumentException("Unknown code: " + code);
// 能力接口检测:示例 - 如果是 AsyncHandler 则提交线程池
if(h instanceof AsyncHandler){
// 提交到线程池执行(示例,同步返回 id)
asyncExecutor.submit(() -> {
try { h.handle(ctx);} catch(Exception e) { log.error(e.getMessage(), e);} });
return;
}
// 幂等:在 preHandle 或 Dispatcher 上统一处理 idempotent
if(h instanceof IdempotentHandler){
// do idempotent check
}
// 直接调用
h.handle(ctx);
}
}
注:
asyncExecutor、幂等校验、限流、重试等建议在 Dispatcher 层或通过 AOP 实现,不要让业务 Handler 负责这些横切关注点。
3.4 运行示例(Controller)
java
@RestController
@RequestMapping("/promo")
public class PromoController {
@Autowired
HandlerDispatcher dispatcher;
@PostMapping("/apply/{code}")
public ResponseEntity apply(@PathVariable String code, @RequestBody PromotionContext ctx){
try{
dispatcher.dispatch(code, ctx);
return ResponseEntity.ok(ctx.getExtra());
} catch (Exception e){
return ResponseEntity.status(500).body(e.getMessage());
}
}
}
示例请求:
POST /promo/apply/THRESHOLD_DISCOUNT
{ "userId":"u1", "orderId":"o1", "amount": 120 }
返回:`{ "discount": 10 }
四、能力扩展实现
4.1 方案一:责任链能力增强
将所有能力抽象成"HandlerFilter",按照顺序处理请求
类似 Spring MVC FilterChain / Gateway FilterChain / Netty Pipeline
4.1.1 定义责任链过滤器接口
java
public interface HandlerFilter {
/**
* @param handler 真实 Handler
* @param ctx 入参
* @param chain 下一过滤器
*/
void doFilter(IHandler handler, PromotionContext ctx, HandlerFilterChain chain);
}
4.1.2 责任链实现
java
public class HandlerFilterChain {
private final List<HandlerFilter> filters;
private int index = 0;
public HandlerFilterChain(List<HandlerFilter> filters) {
this.filters = filters;
}
public void doFilter(IHandler handler, PromotionContext ctx) {
if (index < filters.size()) {
HandlerFilter f = filters.get(index++);
f.doFilter(handler, ctx, this);
} else {
handler.handle(ctx);
}
}
}
4.1.3 把能力封装成 Filter
异步 Filter
java
@Component
public class AsyncFilter implements HandlerFilter {
@Autowired
ExecutorService executor;
@Override
public void doFilter(IHandler handler, PromotionContext ctx, HandlerFilterChain chain) {
if (!(handler instanceof AsyncHandler)) {
chain.doFilter(handler, ctx);
return;
}
executor.submit(() -> chain.doFilter(handler, ctx));
}
}
幂等 Filter
java
@Component
public class IdempotentFilter implements HandlerFilter {
@Autowired
RedisTemplate<String, String> redis;
@Override
public void doFilter(IHandler handler, PromotionContext ctx, HandlerFilterChain chain) {
if (!(handler instanceof IdempotentHandler)) {
chain.doFilter(handler, ctx);
return;
}
String key = "idempotent:" + handler.code() + ":" + ctx.getBizId();
Boolean ok = redis.opsForValue().setIfAbsent(key, "1", Duration.ofMinutes(10));
if (!Boolean.TRUE.equals(ok)) {
log.warn("幂等校验命中:{}", key);
return; // 直接终止
}
chain.doFilter(handler, ctx);
}
}
重试 Filter
java
@Component
public class RetryFilter implements HandlerFilter {
@Override
public void doFilter(IHandler handler, PromotionContext ctx, HandlerFilterChain chain) {
if (!(handler instanceof RetryableHandler retry)) {
chain.doFilter(handler, ctx);
return;
}
int times = retry.retryTimes();
long backoff = retry.backoffMs();
for (int i = 1; i <= times; i++) {
try {
chain.doFilter(handler, ctx);
return;
} catch (Exception e) {
if (i == times) throw e;
Thread.sleep(backoff * i);
}
}
}
}
限流 Filter
java
@Component
public class RateLimitFilter implements HandlerFilter {
@Autowired
RedisTemplate<String, String> redis;
@Override
public void doFilter(IHandler handler, PromotionContext ctx, HandlerFilterChain chain) {
if (!(handler instanceof RateLimitedHandler rate)) {
chain.doFilter(handler, ctx);
return;
}
String key = "rate:" + handler.code();
Long count = redis.opsForValue().increment(key);
redis.expire(key, 1, TimeUnit.SECONDS);
if (count > rate.qps()) {
log.warn("限流触发 > {}", rate.qps());
return;
}
chain.doFilter(handler, ctx);
}
}
4.1.4 Dispatcher
java
@Component
public class PromotionDispatcher {
private final Map<String, IHandler> handlerMap;
private final List<HandlerFilter> filters;
@Autowired
public PromotionDispatcher(List<IHandler> handlers, List<HandlerFilter> filters) {
this.filters = filters;
handlerMap = handlers.stream().collect(Collectors.toMap(IHandler::code, h -> h));
}
public void dispatch(String code, PromotionContext ctx) {
IHandler handler = handlerMap.get(code);
HandlerFilterChain chain = new HandlerFilterChain(filters);
chain.doFilter(handler, ctx);
}
}
4.2 方案二:使用 AOP 实现能力增强(零侵入)
不改 Dispatcher,不改 Handler,只用注解实现
最适合框架化封装、中间件开发
4.2.1 定义注解
java
// 异步
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AsyncAbility {}
// 幂等
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IdempotentAbility {
String key();
}
// 重试
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RetryAbility {
int times() default 3;
long backoff() default 200;
}
// 限流
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimitAbility {
int qps();
}
4.2.2 切面统一处理
只展示幂等示例:
java
@Aspect
@Component
public class IdempotentAspect {
@Autowired
RedisTemplate<String, String> redis;
@Around("@annotation(ann)")
public Object around(ProceedingJoinPoint pjp, IdempotentAbility ann) throws Throwable {
String key = ann.key();
Boolean ok = redis.opsForValue().setIfAbsent(key, "1", Duration.ofMinutes(10));
if (!Boolean.TRUE.equals(ok)) {
log.info("幂等拦截 {}", key);
return null;
}
return pjp.proceed();
}
}
4.2.3 具体Handler
java
@AsyncAbility
@IdempotentAbility(key = "promo:user:{{ctx.userId}}")
@RetryAbility(times = 3)
@RateLimitAbility(qps = 50)
public void doHandle(PromotionContext ctx) {
}
五、架构图
typescript
+-------------------+
| 外部调用入口 |
| (Controller / MQ) |
+---------+---------+
|
v
+-------------------+
| HandlerDispatcher (注册式工厂) |
| - Map<code, IHandler> |
| - 注入 List<IHandler> & List<Filter>|
+---------+---------+
|
v
+-----------------------------------------------+
| HandlerFilterChain / Filters (可选) |
| (责任链:按顺序执行能力过滤器/增强器) |
| - RateLimitFilter (限流, Redis) |
| - IdempotentFilter (幂等, Redis/DB) |
| - AuthFilter (权限校验) |
| - RetryFilter (重试包装/回退策略) |
| - AsyncFilter (异步提交/线程池/MQ) |
+----------------+------------------------------+
|
v
(责任链末端调用 -> 真正的 Handler 执行点)
|
v
+-------------------------------------------------------------+
| IHandler (接口) |
| + getCode() : String |
| + handle(ctx: Context) : void |
+---------------------------+---------------------------------+
|
v
+-------------------------------------------------------------+
| AbstractHandler (模板方法) |
| - final handle(ctx) { |
| preHandle(ctx); // 共性前置(日志、trace) |
| doHandle(ctx); // 子类实现的业务方法 |
| postHandle(ctx); // 共性后置(审计、metrics)}|
| - protected abstract doHandle(ctx) |
+---------------------------+---------------------------------+
|
+----------------+-------------------------------+
| | | |
v v v v
+----------------+ +----------------+ +----------------+ +----------------+
| CouponHandler | | SmsHandler | | OrderHandler | | OtherHandler |
| (Concrete) | | (Concrete) | | (Concrete) | | (Concrete) |
| @Idempotent | | @Async | | @RetryAbility | | @RateLimited |
| @RetryAbility | | @RateLimited | | | | |
+----------------+ +----------------+ +----------------+ +----------------+
(注:子类业务方法 doHandle() 上用注解或直接实现能力接口)
5.1 执行时序(简明步骤)
- 外部调用(HTTP / MQ)→
HandlerDispatcher.dispatch(code, ctx)。 - Dispatcher 根据 code 从注册表取到对应
IHandler。 - Dispatcher 构造
HandlerFilterChain(或直接使用注入的 Filter 列表)。 - 责任链按顺序执行:
RateLimitFilter(Redis 令牌桶) → 拒绝或通过IdempotentFilter(SETNX 或 DB 唯一键) → 已处理则短路AuthFilter(权限/参数校验)RetryFilter(包装后续调用,负责重试与退避)AsyncFilter(若标记异步则提交线程池或MQ,并决定是否等待/返回)
- 责任链末端调用
AbstractHandler.handle()(模板方法):- preHandle(通用前置,如 traceId、日志)
- doHandle(子类业务;此处被 AOP 注解拦截的横切能力也作用于该方法)
- postHandle(通用后置,如埋点、清理)
- 子类
doHandle()可被 AOP 注解(@IdempotentAbility、@RetryAbility、@RateLimitAbility、@AsyncAbility)进一步拦截或增强(二选一:责任链或 AOP 实现能力)。 - 处理过程中会使用:
ExecutorService/ThreadPool(异步执行)Redis(幂等 + 限流 + 分布式锁)MQ(异步/补偿/消息队列)DB(落库、唯一索引)Monitoring(metrics/ELK/Tracing)
六、命名规范
- Handler 类名:
<业务场景><策略>Handler,例如OrderThresholdDiscountHandler、CouponIssueHandler - Code 命名:
UPPER_SNAKE或Dot.Delimited
七、常见坑与防护
- Handler 内部做横切逻辑(如重试/限流):会导致重复实现,建议统一在 Dispatcher/AOP
- Handler 过多依赖注入 :抽象类注入过多 service 导致耦合高,建议把公共服务封装成
HandlerSupport小工具类 - getCode 冲突:注册时需校验重复 code,防止覆盖
- 序列化/Context 版本管理:Context 字段变动需兼容老版本
- 异常吞掉:模板方法中不要吞掉异常,必须做好日志和告警