版本
- 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 設計模式