模板方法与工厂模式实践——一套通用交易执行模型

模板方法与工厂模式实践------一套通用交易执行模型

交易系统贯穿了电商、金融等业务核心,是许多公司的命脉。但很多系统中常见的"大 Service"问题让人头疼------几千行代码包揽所有交易逻辑,动辄牵一发而动全身。本文将介绍如何运用模板方法模式与工厂模式组合优化交易处理逻辑,让核心业务变得结构清晰、扩展自如。


一、你的交易系统是不是也面临这些问题?

  • 职责混杂:所有交易逻辑集中在一个类中,耦合严重。
  • 重复实现:不同交易类型拥有类似但重复的流程代码。
  • 扩展困难:每新增一个交易类型,都要修改原有流程,改动成本高。

问题本质在于:流程骨架与交易细节耦合过深。我们需要一套机制将流程与实现解耦,并具备良好的扩展能力。


二、模板方法+工厂模式组织代码

以电商购物车下单为例,用户可能选择钱包支付、支付宝支付或微信支付等多种方式。我们这套设计将交易处理巧妙地分层,实现了高度解耦和灵活扩展:

统一入口

这一层就像一个智能路由,将请求精准地"分发"给对应的具体交易处理器。还可以处理所有交易类型的通用逻辑,比如权限校验参数校验

java 复制代码
// 核心思想:根据业务类型,获取并调用相应的交易处理模板
combineOrderFactory.get(bizType).doCombineHandler(request);

一行代码就能根据业务类型(bizType)获取并调用相应的交易处理模板。这里的combineOrderFactory正是工厂模式的精髓,它隐藏了对象创建的复杂性,让调用者只关心如何使用。

业务流程层

这一层运用模板方法模式 ,定义了交易的核心流程,就好比搭好了整个交易处理的"骨架"。它通过final方法确定固定不变的流程,并通过钩子方法 ,规定了交易必须遵循的步骤,同时允许子类根据需要重写或扩展某些步骤,从而实现了流程的标准化和细节的定制化


三、实战:用代码落地模板+工厂组合设计

1. 模板方法模式伪代码示例:搭好"骨架"

java 复制代码
public abstract class AbstractTransactionTemplate {

    // final方法,定义了不可改变的交易主流程,确保核心流程的稳定性
    public final void process(Request request) {
        // 钩子方法:预校验,子类可按需重写,检查库存、用户状态等
        verification(request);
        
        // 钩子方法:保存订单或流水,子类可按需重写
        saveOrder(request);

        // 统一预处理:模板里有部分固定实现(如补全信息),并提供钩子给子类扩展
        beforeTransaction(request);
        
        // 核心交易逻辑:发送账务系统。在大部分实际账务系统中,
        // 这一步往往是固定的、可复用的,因为最终都是与统一的账务模块交互。
        transaction(request); 
        
        // 钩子方法:交易完成后的处理,比如发送交易成功通知、更新订单状态
        afterTransaction(request);
    }

    // 默认空实现,子类可按需重写,实现定制化逻辑
    protected void verification(Request request) {}
    protected void saveOrder(Request request) {}
    
    // 预处理方法:模板中包含固定实现,并提供子类扩展钩子
    protected void beforeTransaction(Request request) {
        // 模板中的固定实现:例如,统一补全一些交易上下文信息
        System.out.println("模板:统一补全交易信息...");
        // 调用子类钩子方法,允许子类完善特定逻辑
        onBeforeTransaction(request);
    }

    // 子类可实现的预处理钩子方法
    protected void onBeforeTransaction(Request request) {}

    // 核心交易逻辑,通常是固定的、复用的,这里我们假设它是一个统一的内部调用。
    // 如果有特殊场景需要差异化处理,可以将其改为抽象方法。
    protected void transaction(Request request) {
        // 模拟调用统一的账务核心系统进行实际扣款或入账操作
        // AccountCoreService.process(request.getAmount(), request.getUserId(), request.getBizType());
        System.out.println("统一调用账务核心系统处理交易...");
    }
    
    protected void afterTransaction(Request request) {}
}

2. 工厂模式:自动化注册与获取模板

为了让不同的交易类型能够灵活地被调用,我们结合 Spring 容器,利用注解实现模板的自动注册,让扩展变得异常简单:

java 复制代码
// 1. 定义一个注解,用于标识不同的交易类型
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TransactionType {
    String value(); // 交易类型标识
}
java 复制代码
// 2. 实现工厂类,扫描并注册带有@TransactionType注解的模板
@Component // 让Spring管理这个工厂
public class TransactionTemplateFactory implements ApplicationContextAware, InitializingBean {

    private final Map<String, AbstractTransactionTemplate> templateMap = new HashMap<>();
    private ApplicationContext applicationContext;
    private static final Logger log = LoggerFactory.getLogger(TransactionTemplateFactory.class);

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        this.applicationContext = context;
    }

    @Override
    public void afterPropertiesSet() {
        // 获取Spring容器中所有AbstractTransactionTemplate的子类实例
        Map<String, AbstractTransactionTemplate> beans = applicationContext.getBeansOfType(AbstractTransactionTemplate.class);
        for (AbstractTransactionTemplate bean : beans.values()) {
            // 检查是否有@TransactionType注解
            TransactionType annotation = bean.getClass().getAnnotation(TransactionType.class);
            if (annotation != null) {
                String type = annotation.value();
                // 将类型与模板实例映射,实现自动注册
                templateMap.put(type, bean);
                log.info("注册交易模板:{} -> {}", type, bean.getClass().getSimpleName());
            } else {
                log.warn("TransactionTemplate {} 未声明 @TransactionType 注解,已跳过注册", bean.getClass().getSimpleName());
            }
        }
    }

    // 静态方法,根据类型获取对应的模板实例,供外部调用
    public AbstractTransactionTemplate get(String type) {
        AbstractTransactionTemplate template = templateMap.get(type);
        if (template == null) {
            throw new IllegalArgumentException("未找到匹配的交易模板类型:" + type);
        }
        return template;
    }
}

使用起来异常简洁:

java 复制代码
// 假设这是你的交易入口Service
@Service
public class TradeService {

    @Autowired
    private TransactionTemplateFactory factory;

    public void processTrade(TradeRequest request) {
        // 根据请求中的类型,通过工厂获取对应的交易模板
        AbstractTransactionTemplate template = factory.get(request.getType());
        // 调用模板的process方法,执行整个交易流程
        template.process(request);
    }
}

3. 案例:扩展新交易类型------只需三步,轻松搞定"积分支付"!

想象一下,现在产品经理又要新增一种"积分支付 "的交易类型。如果还是老旧的"一锅烩"代码,你可能得在现有 Service 里加一大段if-else if,甚至复制粘贴大量逻辑,小心翼翼地改动。

而有了这套模式,新增一种交易类型简直是享受:

  1. 新建一个类,继承 AbstractTransactionTemplate :比如 PointPaymentTransactionTemplate
  2. 按需重写钩子方法 :例如,verification() 方法里可能需要额外校验用户积分是否足够;而针对积分支付特有的预处理逻辑,你只需要实现 onBeforeTransaction() 方法核心的 transaction() 方法则完全不用管,它会继续调用统一的账务核心系统。
  3. 加上注解 @TransactionType("积分支付")
java 复制代码
@TransactionType("积分支付")
public class PointPaymentTransactionTemplate extends AbstractTransactionTemplate {

    @Override
    protected void verification(Request request) {
        super.verification(request); // 调用父类通用校验
        // 增加积分支付特有的校验逻辑:例如检查用户积分余额
        // if (request.getUserId().getPoints() < request.getAmount()) {
        //     throw new BusinessException("积分不足");
        // }
        System.out.println("执行积分支付特有校验...");
    }

    @Override
    protected void onBeforeTransaction(Request request) {
        // 积分支付特有的预处理:例如预扣除用户积分
        // PointService.preDeduct(request.getUserId(), request.getAmount());
        System.out.println("执行积分支付特有预处理(在统一补全信息后)...");
    }

    // transaction() 方法由父类统一实现,无需在此重写,直接复用核心账务处理
    // afterTransaction() 等也按需重写或直接继承
}

看,无需修改TradeService或任何现有核心代码,新的"积分支付"类型就完美融入了系统!这正是开闭原则(对扩展开放,对修改关闭)的完美体现。


四、交易模板核心流程一览

下面是交易模板process()方法的核心流程图。

graph TD subgraph 交易主流程 process方法 A["统一入口"] --> B("获取特定交易模板"); B --> C{"调用 process 方法"}; C --> D{"verification()
可重写"}; D --> E{"saveOrder()
可重写"}; E --> F["beforeTransaction()
包含固定逻辑,调用 onBeforeTransaction()"]; F --> F1{"onBeforeTransaction()
可重写"}; F1 --> G["transaction()
固定复用"]; G --> H{"afterTransaction()
可重写"}; end style C fill:#f9f,stroke:#333,stroke-width:2px; style D fill:#afe,stroke:#333,stroke-width:2px; style E fill:#afe,stroke:#333,stroke-width:2px; style F fill:#ADD8E6,stroke:#333,stroke-width:2px; style F1 fill:#afe,stroke:#333,stroke-width:2px; style G fill:#DAF7A6,stroke:#333,stroke-width:2px; style H fill:#afe,stroke:#333,stroke-width:2px; classDef reusable fill:#DAF7A6,stroke:#333,stroke-width:2px; classDef hook fill:#afe,stroke:#333,stroke-width:2px; classDef fixedTemplate fill:#ADD8E6,stroke:#333,stroke-width:2px; class G reusable; class D,E,F1,H hook; class F fixedTemplate;
相关推荐
Java技术小馆26 分钟前
利用DeepWiki高效阅读项目源码
java·后端·面试
PetterHillWater1 小时前
电商行业商品标题分词实践
后端
萌新小码农‍1 小时前
SpringBoot新闻项目学习day3--后台权限的增删改查以及权限管理分配
spring boot·后端·学习
想用offer打牌2 小时前
一站式了解责任链模式🥹
后端·设计模式·架构
小码编匠2 小时前
面向工业应用的点云相机控制接口库(含C#调用示例)
后端·c#·.net
Luffe船长2 小时前
springboot将文件插入到指定路径文件夹,判断文件是否存在以及根据名称删除
java·spring boot·后端·spring
程序员清风4 小时前
RocketMQ发送消息默认是什么策略,主同步成功了就算成功了?异步写?还是要大部分从都同步了?
java·后端·面试
罗政4 小时前
小区物业管理系统源码+SpringBoot + Vue (前后端分离)
vue.js·spring boot·后端
杨同学technotes4 小时前
Spring Kafka进阶:实现多态消息消费
后端·kafka
雨中散步撒哈拉4 小时前
3、做中学 | 二年级上期 Golang数据类型和常量/变量声明使用
开发语言·后端·golang