模板方法模式在支付場景中的最佳實踐

版本

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章