模板方法模式在支付场景中的最佳实践

版本
  • SpringBoot:2.2.5.RELEASE
  • Jdk:1.8
  • Maven:3.5.2
  • Idea:2019.3
要点
  • 模板方法定义了算法的步骤,把这些步骤的实现延迟到子类
  • 模板方法模式为我们提供了一种代码复用的重要技巧
  • 模板方法的抽象类可以定义具体方法、抽象方法和钩子
  • 抽象方法由子类实现
  • 钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,子类可以选择要不要去覆盖它
  • 为了防止子类改变模板方法中的算法,可以将模板方法声明为final
  • 策略模式和模板方法模式都封装算法,一个用组合,一个用继承
  • 工厂方法是模板方法的一种特殊版本
简介

模板方法模式在一个方法中定义了一个算法的骨架,模板就是一个方法。更具体得说,这个方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,由子类负责实现。这可以确保算法的结构保持不变,同时由子类提供部分实现

食用

0:定义一个算法(业务方法)
不论是哪种支付方式支付,对应的支付步骤大体是一致且顺序固定的,比如支付前先记录日志,接着创建支付单,然后做些前置操作,之后发起支付,最后处理支付结果等。因此我们可以定义一个模板来完成支付动作

/**
 * @author liujiazhong
 */
public interface PayService {

    /**
     * 支付
     * @param request 支付请求参数
     * @return 支付结果
     */
    PayRespBO pay(PayReqBO request);

}

1:在抽象类中定义一个支付模板
模板中定义了支付过程中的各个步骤和顺序,其中各种支付方式都一样的操作我们可以将方法定义为final;各个支付方式都不一样的操作,定义为abstract,必须由子类实现;再比如创建支付单,在抽象类中实现一个基础的支付单创建,子类需要创建自己的支付单的话就由子类重写该方法;再比如记录消费流水这个步骤,有些支付方式不需要记录消费流水有些则需要,因此在抽象类中我们可以将记录消费流水这个步骤定义成空实现,子类根据需要来实现该步骤

/**
 * @author liujiazhong
 */
@Slf4j
@Service
public abstract class AbstractPayServiceImpl implements PayService {

    @Override
    public PayRespBO pay(PayReqBO request) {
        // Step0: record log
        log(request);
        // Step1: create payment
        OrderPayment payment = createPayment(request);
        // Step2: before pay
        beforePay(request);
        // Step3: pay
        PayRespBO result = doPay(request, payment);
        // Step4: after pay
        afterPay(request);
        // Step5: handle pay result
        return handleResult(request, result);
    }

    protected final void log(PayReqBO request) {
        log.info("record pay log:{}", request.getOrderCode());
        // todo record log to mysql/redis/elasticsearch

    }

    protected OrderPayment createPayment(PayReqBO request) {
        log.info("create payment:{}", request.getOrderCode());
        // todo save payment into mysql

        return OrderPayment.builder().id(1001L).paymentType("base").build();
    }

    protected void beforePay(PayReqBO request) {
        log.info("before pay:{}", request.getOrderCode());
    }

    /**
     * do pay
     * @param request request param
     * @param payment order payment
     * @return pay result
     */
    protected abstract PayRespBO doPay(PayReqBO request, OrderPayment payment);

    protected void afterPay(PayReqBO request) {
        log.info("after pay:{}", request.getOrderCode());
    }

    protected PayRespBO handleResult(PayReqBO request, PayRespBO result) {
        log.info("handle pay result:orderCode:{}, paymentType:{}, result:{}", request.getOrderCode(), result.getPaymentType(), result.getStatus());
        // todo handle pay result
        return PayRespBO.builder().status("success").build();
    }

}

2:本地支付
对于本地支付,我们只有真正支付这一步不同吗,其他都可以直接使用抽象类中的基本步骤,比如记录日志,创建基本的支付单等,因此,在本地支付实现类中,我们只需要实现抽象类中支付这一步即可

/**
 * @author liujiazhong
 */
public interface LocalPayService extends PayService {

    /**
     * 本地支付方式支付
     * @param request 本地支付请求参数
     * @return 本地支付结果
     */
    PayRespBO localPay(PayReqBO request);

}

本地支付实现类

/**
 * @author liujiazhong
 */
@Slf4j
@Service
public class LocalPayServiceImpl extends AbstractPayServiceImpl implements LocalPayService {

    @Override
    public PayRespBO localPay(PayReqBO request) {
        log.info("use local pay:{}", request.getOrderCode());
        // todo pay

        return PayRespBO.builder().paymentType("local").status("success").build();
    }

    @Override
    protected PayRespBO doPay(PayReqBO request, OrderPayment payment) {
        return localPay(request);
    }

}

3:刷卡支付
本案例中的刷卡支付,有两个步骤需要重写,一个是创建支付单,创建支付单的时候需要创建刷卡支付类型的支付单;另一个是支付,支付对应刷卡支付。由此可见,支付操作是每种支付必须实现的步骤,创建支付单是各个支付方式选择实现的步骤

/**
 * @author liujiazhong
 */
public interface BankPayService extends PayService {

    /**
     * 银联支付方式支付
     * @param request 银联支付请求参数
     * @return 银联支付结果
     */
    PayRespBO bankPay(PayReqBO request);

}

刷卡支付实现类

/**
 * @author liujiazhong
 */
@Slf4j
@Service
public class BankPayServiceImpl extends AbstractPayServiceImpl implements BankPayService {

    @Override
    public PayRespBO bankPay(PayReqBO request) {
        log.info("use bank pay:{}", request.getOrderCode());
        // todo pay

        return PayRespBO.builder().paymentType("bank").status("success").build();
    }

    private OrderPayment createBankPayment(PayReqBO request) {
        log.info("create bank payment:{}", request.getOrderCode());
        // todo create bank payment

        return OrderPayment.builder().id(1001L).paymentType("bank").build();
    }

    @Override
    protected OrderPayment createPayment(PayReqBO request) {
        return createBankPayment(request);
    }

    @Override
    protected PayRespBO doPay(PayReqBO request, OrderPayment payment) {
        return bankPay(request);
    }
}

4:结果测试
这里注意,我们支付的时候直接调用最开始我们定义的模板方法,而不是各自的支付方法

/**
 * @author liujiazhong
 */
@Slf4j
@RestController
public class DemoController {

    private final LocalPayService localPayService;
    private final BankPayService bankPayService;

    public DemoController(LocalPayService localPayService, BankPayService bankPayService) {
        this.localPayService = localPayService;
        this.bankPayService = bankPayService;
    }

    @GetMapping("local")
    public void localPay() {
        localPayService.pay(PayReqBO.builder().orderId(100001L).orderCode("TEST100001").paymentType("local").userId(1001L).build());
    }

    @GetMapping("bank")
    public void bankPay() {
        bankPayService.pay(PayReqBO.builder().orderId(100001L).orderCode("TEST100001").paymentType("bank").userId(1001L).build());
    }

}

日志输出
http://localhost:8084/local
在这里插入图片描述
http://localhost:8084/bank
在这里插入图片描述
日志输出可见,重写了模板中的步骤方法后,执行时就会使用重写后的实现,子类没有重写的则使用抽象类中的实现

链接

模板方法:https://zh.wikipedia.org

参考

Head First 设计模式

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值