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

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


文档目的

本档面向开发团队,详细说明一种可复用的架构模式:策略模式 + 模板方法 + 注册式工厂(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. 异常吞掉:模板方法中不要吞掉异常,必须做好日志和告警
相关推荐
平凡之路无尽路21 小时前
智能体设计模式:构建智能系统的实践指南
人工智能·设计模式·自然语言处理·nlp·aigc·vllm
冷崖1 天前
工厂模式-创建型
c++·设计模式
何中应2 天前
【面试题-5】设计模式
java·开发语言·后端·设计模式·面试题
沐森2 天前
在实战中运用泛型和动态trait(特质)
设计模式
lomocode2 天前
改一个需求动 23 处代码?你可能踩进了这个坑
后端·设计模式
喷火龙8号2 天前
JWT 认证方案深度对比:单 Token 扩展刷新 vs 双 Token 验证
后端·设计模式·架构
fakerth3 天前
【OpenHarmony】设计模式模块详解
c++·单例模式·设计模式·openharmony
alibli3 天前
一文学会设计模式之创建型模式及最佳实现
c++·设计模式
1024肥宅3 天前
前端常用模式:提升代码质量的四大核心模式
前端·javascript·设计模式
郝学胜-神的一滴3 天前
设计模式依赖于多态特性
java·开发语言·c++·python·程序人生·设计模式·软件工程