策略模式 + 模板方法 + 注册式工厂 统一设计方案(营销优惠场景示例)


文档目的

本档面向开发团队,详细说明一种可复用的架构模式:策略模式 + 模板方法 + 注册式工厂(Dispatcher),并在此基础上加入接口能力标签的最佳实践。文档提供:

  • 设计理念与目标
  • 各类职责说明(到类级别)及为什么要这样设计
  • Spring Boot 2.x 实现细节(代码示例)
  • 营销优惠(Promotion)示例工程:从代码骨架到运行与测试说明
  • 可扩展能力(幂等、异步、重试、限流)的实现策略
  • 团队使用规范、命名约定、常见坑

一、设计目标(回顾)

  1. 高可扩展:新增策略时不修改核心框架代码
  2. 低耦合:调用方只依赖接口(IHandler)而非实现
  3. 统一流程:模板方法负责统一流程(前置、处理、后置)
  4. 自动注册:Dispatcher 自动发现并注册 Handler,避免硬编码
  5. 可贴标签扩展能力:通过额外接口(如 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 的兜底策略(抛错、降级)
  • 能力标签接口(可选)

    • 示例:AsyncHandlerIdempotentHandlerRetryableHandlerRateLimited
    • 作用:通过 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
// 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 接口
// promotion-core
public interface IHandler {
String getCode();
void handle(PromotionContext ctx) throws Exception;
}
3.3.3 抽象模板 AbstractHandler
@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 能力接口(示例)
public interface AsyncHandler {}
public interface IdempotentHandler {}
public interface RetryableHandler {}
public interface RateLimitedHandler {}
3.3.5 具体 Handler 示例(满减)
@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
@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)

@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 定义责任链过滤器接口
public interface HandlerFilter {
/**
* @param handler   真实 Handler
* @param ctx       入参
* @param chain     下一过滤器
*/
void doFilter(IHandler handler, PromotionContext ctx, HandlerFilterChain chain);
}
4.1.2 责任链实现
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

@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

@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

@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

@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
@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 定义注解
// 异步
@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 切面统一处理

只展示幂等示例:

@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
@AsyncAbility
@IdempotentAbility(key = "promo:user:{{ctx.userId}}")
@RetryAbility(times = 3)
@RateLimitAbility(qps = 50)
public void doHandle(PromotionContext ctx) {
}

五、架构图

+-------------------+
|   外部调用入口     |
|  (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 执行时序(简明步骤)

  1. 外部调用(HTTP / MQ)→ HandlerDispatcher.dispatch(code, ctx)
  2. Dispatcher 根据 code 从注册表取到对应 IHandler
  3. Dispatcher 构造 HandlerFilterChain(或直接使用注入的 Filter 列表)。
  4. 责任链按顺序执行:
    • RateLimitFilter (Redis 令牌桶) → 拒绝或通过
    • IdempotentFilter (SETNX 或 DB 唯一键) → 已处理则短路
    • AuthFilter(权限/参数校验)
    • RetryFilter(包装后续调用,负责重试与退避)
    • AsyncFilter(若标记异步则提交线程池或MQ,并决定是否等待/返回)
  5. 责任链末端调用 AbstractHandler.handle()(模板方法):
    • preHandle(通用前置,如 traceId、日志)
    • doHandle(子类业务;此处被 AOP 注解拦截的横切能力也作用于该方法)
    • postHandle(通用后置,如埋点、清理)
  6. 子类 doHandle() 可被 AOP 注解(@IdempotentAbility、@RetryAbility、@RateLimitAbility、@AsyncAbility)进一步拦截或增强(二选一:责任链或 AOP 实现能力)。
  7. 处理过程中会使用:
    • ExecutorService / ThreadPool(异步执行)
    • Redis(幂等 + 限流 + 分布式锁)
    • MQ(异步/补偿/消息队列)
    • DB(落库、唯一索引)
    • Monitoring(metrics/ELK/Tracing)

六、命名规范

  • Handler 类名:<业务场景><策略>Handler,例如 OrderThresholdDiscountHandlerCouponIssueHandler
  • Code 命名:UPPER_SNAKEDot.Delimited

七、常见坑与防护

  1. Handler 内部做横切逻辑(如重试/限流):会导致重复实现,建议统一在 Dispatcher/AOP
  2. Handler 过多依赖注入:抽象类注入过多 service 导致耦合高,建议把公共服务封装成 HandlerSupport 小工具类
  3. getCode 冲突:注册时需校验重复 code,防止覆盖
  4. 序列化/Context 版本管理:Context 字段变动需兼容老版本
  5. 异常吞掉:模板方法中不要吞掉异常,必须做好日志和告警