故事
“不能在寫if else來擴充當前系統了,現在已經有三個支付場景了......”工位上,小貓看著電腦,撓著頭。
就在剛剛,小貓接到了一個新需求,需要和客戶公司打通資產,形成資產聯動。說白了就是需要定製化對接客戶公司的支付資產體系。除了這次接到的之外。前面其實已經對接了三家了。由於每家對接規範都不一樣,歷史對接的時候為了儘快上線,都是直接搞個else的新路由分支,然後去實現支付,退款。
在小貓看來,就是在堆屎山。牽一髮而動全身的感覺真的很不好。由於本次的需求留有的時間還是相當充裕的,所以小貓下定決心,打算利用這次的擴充,將原來不合理的地方用上設計模式將其重構掉。
深思熟慮很久,小貓下定決心打算用“策略模式”重構一番。
聊聊策略模式
說到策略模式,老貓覺得這種設計模式在實際開發中使用其實是相當頻繁的。老貓工作到現在也在很多業務場景中使用過這樣的設計模式。例如,上述小貓遇到的第三方支付整合的問題上。另外的還有商城搞活動,針對不同的使用者下單行為提供不同的折扣或者返現等活動。再例如商城運營人員根據不同的加價策略去定在售商品的價格等。
老貓工作十年中,對接過很多外部企業或者單位的介面,若業務定義一樣,只是介面協議不同的業務其實往往都可以用到策略模式。
提煉一下適用場景如下:
(1)系統中有很多類,而它們的區別僅僅在於行為不同。
(2)一個系統需要動態地在幾種演算法中選擇一種。
在很多業務中,這種模式用起來真的很香,既能夠擺脫成堆的“if else”(當然關於 if else的最佳化,又是另外一個故事了,有興趣的小夥伴可以看看這篇文章【接手了個專案,被if..else搞懵逼了】),另外寫出來的程式碼本身擴充性也會比較好。
那麼我們來看看策略模式,並且基於小貓遇到的場景問題,咱們來擼一下實現程式碼。
策略模式解決多路支付通道問題
在定義支付行為的時候,我們首先定義出常規的支付行為,咱們可以用介面interface的形式定義出來,當然也可以用abstract類的方式定義出來。這裡老貓使用後者來定義。程式碼如下:
/**
* @author 公眾號:程式設計師老貓
*/
public abstract class Payment {
//獲取支付渠道的名稱
public abstract String getName();
//查詢使用者餘額
protected abstract BigDecimal queryBalance(String uid);
public PayState doPay(String uid, BigDecimal amount) {
if (queryBalance(uid).compareTo(amount) < 0) {
return new PayState(500, "支付失敗", "賬戶餘額不足");
}
return new PayState(200, "支付成功", "支付金額:" + amount);
}
}
定義一個標準的支付狀態類:
/**
* @author 公眾號:程式設計師老貓
*/
public class PayState {
private int code;
private String msg;
private Object data;
public PayState(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public String toString() {
return ("pay state :[" + code + "]," + msg + ",order detail: " + data);
}
}
接下來,咱們來模擬各個支付渠道,並且咱們能夠知道在不同的支付渠道中,我們當前的賬戶餘額是多少。咱們就拿用的比較多的微信、支付寶、京東支付等支付渠道來做模擬吧。
支付寶實現,並且賬戶中有900元:
public class AliPay extends Payment {
@Override
public String getName() {
return "支付寶";
}
@Override
protected BigDecimal queryBalance(String uid) {
return new BigDecimal(900);
}
}
微信支付,並且賬戶中有300元:
public class WxPay extends Payment{
@Override
public String getName() {
return "微信";
}
@Override
protected BigDecimal queryBalance(String uid) {
return new BigDecimal(300);
}
}
以此類推,京東支付。
public class JDPay extends Payment{
@Override
public String getName() {
return "京東白條";
}
@Override
protected BigDecimal queryBalance(String uid) {
return new BigDecimal(400);
}
}
定義好各種單一支付通道之後,其實我們就要組裝策略了。把上述支付通道,載入到策略路由類中。老貓覺得這個地方也是策略模式中比較核心的點。
/**
* @author 公眾號:程式設計師老貓
*/
public class PayStrategy {
public static final String ALI_PAY = "aliPay";
public static final String WX_PAY = "wxPay";
public static final String JD_PAY = "jdPay";
public static final String DEFAULT = "wxPay";
//初始化的時候裝載支付行為策略
private static Map<String,Payment> paymentMap = new HashMap<>();
static {
paymentMap.put(ALI_PAY,new AliPay());
paymentMap.put(WX_PAY,new WxPay());
paymentMap.put(JD_PAY,new JDPay());
paymentMap.put(DEFAULT,new WxPay());
}
//呼叫的時候路由具體的支付策略
public static Payment get(String payKey){
if(!paymentMap.containsKey(payKey)){
return paymentMap.get(DEFAULT);
}
return paymentMap.get(payKey);
}
}
接下來,我們就模擬使用者下訂單支付行為了,具體如下:
/**
* @author 程式設計師老貓
* 下單場景
*/
public class Order {
private String uid; //使用者Id
private String orderId; //訂單Id
private BigDecimal orderAmount; //支付金額
public Order(String uid, String orderId, BigDecimal orderAmount) {
this.uid = uid;
this.orderId = orderId;
this.orderAmount = orderAmount;
}
public PayState doPay() {
return doPay(PayStrategy.DEFAULT);
}
public PayState doPay(String payKey) {
Payment payment = PayStrategy.get(payKey);
System.out.println("歡迎使用" + payment.getName());
System.out.println("本次交易金額:" + orderAmount);
return payment.doPay(uid, orderAmount);
}
}
最終咱們來進行測試一下:
public class PayStrategyTest {
public static void main(String[] args) {
Order order = new Order("ktdaddy","20240425224901",new BigDecimal(245));
System.out.println(order.doPay(PayStrategy.ALI_PAY));
}
}
結果輸出:
歡迎使用支付寶
本次交易金額:245
pay state :[200],支付成功,order detail: 支付金額:245
上述基本就是策略模式的使用了。老貓覺得應該還是比較清晰的。咱們簡單看一下最終的呼叫類圖:
到這裡很多小夥伴可能會問了,上面寫的案例其實並沒有結合我們實際的spring開發框架去實現策略模式,日常開發的過程中我們Java程式設計師主要用的還是spring框架。那麼如果要結合咱們spring日常開發框架又是怎麼去實現呢。那麼接下來,咱們接著往下看。
SpringBoot下策略模式解決多路支付通道
其實核心的思想還是上面這幾個要領,老貓在此不多做展開,只是給大家提供一些思路,然後提供一些簡單的日常開發中使用的截圖給大家參考。支付使用策略模式的核心的思想無非就下面兩個。
(1)咱們需要不同的支付策略類。
(2)需要有路由支付策略類的路由類。
其實上面兩個核心中,比較重要的還是第二點,咱們如果去初始化策略類。在上面案例中,老貓使用的靜態方法塊來裝載各個策略方法。在spring中其實我們可以使用@PostConstruct註解,進行service策略的初始化裝載。
如下首先定義一個標準的支付介面,並且實現一下:
public interface Payment {
//獲取支付渠道的名稱
String getCode();
PayState doPay(String uid, BigDecimal amount);
}
然後實現這個介面,咱們舉一個例子來說明
@Service
public class JDPay implements Payment {
@Override
public String getCode() {
return "jdPay";
}
@Override
public PayState doPay(String uid, BigDecimal amount) {
return null;
}
}
關鍵此時咱們看一下核心載入的地方。
/**
* 程式設計師老貓
**/
@Service
public class PayStrategy {
@Autowired
private Payment[] payments;
//初始化的時候裝載支付行為策略
private static Map<String, Payment> paymentMap = new ConcurrentHashMap<>();
@PostConstruct
private void initRouteMap() {
for (Payment externalPayService : payments) {
paymentMap.put(externalPayService.getCode(), externalPayService);
}
}
public Payment getPayment(String payCode) {
return paymentMap.get(payCode);
}
}
上述就是結合spring的核心策略模式的實現方式,老貓這裡沒有展開,但是最精華的部分,老貓覺得已經說清楚了。當然基於@PostConstruct進行策略載入的方式只是一種。大家可以實現spring自帶的InitializingBean,在 Spring 容器完成 bean 的屬性注入後,會呼叫 afterPropertiesSet() 方法來執行初始化邏輯。
總結
上述主要和大家分享了基於策略模式如何去做支付整合第三方支付的問題。當然這只是一個簡單的案例,其實很多時候我們在實際的業務開發中很多地方都可以用到這樣一個模式。在jdk原始碼中以及spring原始碼中也屢見不鮮。但是策略模式也不是萬能的,存在優點的同時也存在缺點。
優點:
1、策略模式符合開閉原則。(當然有興趣瞭解設計原則的小夥伴歡迎戳【違反這些設計原則,系統就等著“腐爛”】)
2、策略模式可以避免使用多重複的條件語句。例如最佳化if else。當然之前也老貓也寫過類似博文。【接手了個專案,被if..else搞懵逼了】
3、使用策略模式可以提高演算法的保密性和安全性。
缺點:
1、不像介面卡模式,策略模式要求客戶端需要知道所有的策略,並且自行決定使用哪類策略。關於介面卡模式,感興趣的小夥伴可以看這裡【真香定律!我用這種模式重構了第三方登入】
2、策略類會越來越多,維護成本也會越來越高。