在实际业务开发中,很多业务流程的整体骨架是固定不变的,但其中某一步或某几步的具体实现会不一样。
比如:

  • 支付流程:校验订单 → 调用渠道 → 处理结果
  • 审核流程:提交 → 校验 → 审核 → 通知
  • 导出流程:查询数据 → 生成文件 → 上传 → 通知

如果每个实现都写一套完整代码:

  • 大量重复代码(校验、日志、异常、结果处理)
  • 流程不统一,容易写错、漏步骤
  • 维护成本极高,改一次流程要改所有实现

模板方法模式就是为了解决这个问题:

  • 父类定义固定流程骨架(模板方法)
  • 子类只实现变化的那一步
  • 流程统一、代码复用、易扩展、易维护

定义

  • 模板方法模式属于行为型设计模式,核心是:在一个抽象类中定义一个算法的骨架(模板),将算法中可变的步骤延迟到子类中实现。这样可以保证算法的整体结构不变,同时让具体细节可以灵活定制。

步骤1:定义抽象类

/**
 * 支付模板抽象类(定义支付流程的骨架)
 * 真实项目中会放在 service/abstract 包下
 */
@Slf4j
@Component 
public abstract class PaymentAbstractTemplate {

    /**
     * 模板方法:定义支付的整体流程(final 防止子类重写,保证流程不变)
     * @param orderId 订单ID
     * @param amount 支付金额
     * @return 支付结果(统一返回格式)
     */
    public final PaymentResult pay(String orderId, BigDecimal amount) {
        try {
            // 步骤1:校验订单(固定逻辑)
            boolean checkResult = checkOrder(orderId, amount);
            if (!checkResult) {
                return PaymentResult.fail("订单校验失败");
            }

            // 步骤2:调用具体支付渠道(可变逻辑,子类实现)
            String payResponse = callPaymentChannel(orderId, amount);

            // 步骤3:处理支付结果(固定逻辑)
            boolean handleResult = handlePayResult(orderId, payResponse);
            if (!handleResult) {
                return PaymentResult.fail("支付结果处理失败");
            }

            // 步骤4:返回统一结果
            return PaymentResult.success("支付成功", payResponse);
        } catch (Exception e) {
            log.error("支付异常,订单ID:{}", orderId, e);
            return PaymentResult.fail("支付异常:" + e.getMessage());
        }
    }

    /**
     * 固定步骤:校验订单
     */
    private boolean checkOrder(String orderId, BigDecimal amount) {
        // 真实项目中会查询数据库校验订单状态、金额、用户是否匹配等
        log.info("校验订单,订单ID:{},金额:{}", orderId, amount);
        if (orderId == null || orderId.isEmpty() || amount.compareTo(BigDecimal.ZERO) <= 0) {
            return false;
        }
        return true;
    }

    /**
     * 可变步骤:调用具体支付渠道(抽象方法,子类实现)
     */
    protected abstract String callPaymentChannel(String orderId, BigDecimal amount);

    /**
     * 固定步骤:处理支付结果
     */
    private boolean handlePayResult(String orderId, String payResponse) {
        // 真实项目中会更新订单状态为“已支付”、记录支付日志、扣减库存等
        log.info("处理支付结果,订单ID:{},支付响应:{}", orderId, payResponse);
        if (payResponse == null || payResponse.isEmpty()) {
            return false;
        }
        return true;
    }

    /**
     * 支付结果封装类(真实项目中会单独定义,这里简化)
     */
    @Data
    @AllArgsConstructor
    public static class PaymentResult {
        private boolean success;
        private String message;
        private String data;


        public static PaymentResult success(String message, String data) {
            return new PaymentResult(true, message, data);
        }

        public static PaymentResult fail(String message) {
            return new PaymentResult(false, message, null);
        }
    }
}

步骤2:实现具体子类

/**
 * 微信支付实现类(具体支付渠道)
 */
@Slf4j
@Service
public class WxPayTemplate extends PaymentAbstractTemplate {

    @Override
    protected String callPaymentChannel(String orderId, BigDecimal amount) {
        // 真实项目中会调用微信支付的SDK/API,比如统一下单接口
        log.info("调用微信支付接口,订单ID:{},金额:{}", orderId, amount);
        // 模拟微信支付API调用(真实场景会拼接参数、签名、调用HTTP接口)
        String prepayId = "wx" + System.currentTimeMillis(); // 模拟预支付ID
        String wxResponse = String.format("{\"out_trade_no\":\"%s\",\"total_fee\":%d,\"prepay_id\":\"%s\",\"return_code\":\"SUCCESS\"}",
                orderId, amount.multiply(new BigDecimal(100)).intValue(), prepayId);
        log.info("【微信支付】接口返回结果:{}", wxResponse);
        return wxResponse;
    }
}

/**
 * 支付宝支付实现类(具体支付渠道)
 */
@Slf4j
@Service
public class AliPayTemplate extends PaymentAbstractTemplate {

    @Override
    protected String callPaymentChannel(String orderId, BigDecimal amount) {
        // 真实项目中会调用支付宝支付的SDK/API,比如创建交易接口
        log.info("调用支付宝支付接口,订单ID:{},金额:{}", orderId, amount);
        // 模拟支付宝支付API调用
        String tradeNo = "ali" + System.currentTimeMillis(); // 模拟支付宝交易号
        String aliResponse = String.format("{\"out_trade_no\":\"%s\",\"total_amount\":\"%s\",\"trade_no\":\"%s\",\"code\":\"10000\"}",
                orderId, amount, tradeNo);
        log.info("【支付宝支付】接口返回结果:{}", aliResponse);
        return aliResponse;
    }
}

步骤3:定义controller

@Slf4j
@RestController
@RequestMapping("/api/pay")
public class PaymentController {

    @Resource
    private Map<String, PaymentAbstractTemplate> paymentTemplateMap;

    /**
     * 统一支付接口
     * @param payRequestDTO 支付请求参数
    *  {
     *   "orderId": "ORDER_20260305001",
     *   "amount": 99.99,
     *   "payType": "wxpay"
     * }
     * @return 统一返回结果
     */
    @PostMapping("/unifiedPay")
    public String unifiedPay(@RequestBody PayRequestDTO payRequestDTO) {
        try {
            log.info("接收支付请求,参数:{}", payRequestDTO);
            
            // 1. 校验支付类型
            String payType = payRequestDTO.getPayType();
            PaymentAbstractTemplate paymentTemplate;
            if ("wxpay".equals(payType)) {
                paymentTemplate = paymentTemplateMap.get("wxPayTemplate");
            } else if ("alipay".equals(payType)) {
                paymentTemplate = paymentTemplateMap.get("aliPayTemplate");
            } else {
                return "不支持的支付类型:" + payType;
            }

            // 2. 调用模板方法执行支付
            PaymentAbstractTemplate.PaymentResult payResult =
                paymentTemplate.pay(payRequestDTO.getOrderId(), payRequestDTO.getAmount());

            // 3. 返回结果
            if (payResult.isSuccess()) {
                return payResult.toString();
            } else {
                return payResult.getMessage();
            }
        } catch (Exception e) {
            log.error("支付接口异常", e);
            return "支付接口异常:" + e.getMessage();
        }
    }
}

扩展

  • 钩子方法(Hook Method):如果流程中某些步骤是 “可选” 的(比如部分支付渠道需要签名验证,部分不需要),可以在抽象类中定义默认实现的钩子方法,子类可选择重写。例如:
// 钩子方法:是否需要签名验证(默认需要)
protected boolean needSign() {
    return true;
}
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐