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

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


文档目的

本档面向开发团队,详细说明一种可复用的架构模式:策略模式 + 模板方法 + 注册式工厂(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
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 执行时序(简明步骤)

  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. 异常吞掉:模板方法中不要吞掉异常,必须做好日志和告警
相关推荐
触底反弹2 小时前
🤯 面试被问 AI Workflow 和 Agent 有啥区别?3 张图 + 2 段代码讲清楚!
人工智能·设计模式·面试
张小姐的猫20 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
杨充1 天前
10.可测试性实战设计
设计模式·开源·代码规范
杨充1 天前
9.重构十二式的实战
设计模式·开源·代码规范
杨充1 天前
6.设计原则的全景图
设计模式·开源·全栈
杨充1 天前
2.面向对象的特性
设计模式
杨充1 天前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充1 天前
8.反模式与坏味道
设计模式·开源·代码规范
杨充1 天前
3.接口vs抽象类比较
设计模式