不得不說,在很多業務中,這種模式用得真的很香
老貓的設計模式專欄已經偷偷發車了。不甘愿做crud boy?看了好幾遍的設計模式還記不住?那就不要刻意記了,跟上老貓的步伐,在一個個有趣的職場故事中領悟設計模式的精髓吧。還等什么?趕緊上車吧。
故事
“不能再寫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、策略類會越來越多,維護成本也會越來越高。