版本
- 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
日志输出可见,重写了模板中的步骤方法后,执行时就会使用重写后的实现,子类没有重写的则使用抽象类中的实现
链接
参考
Head First 设计模式