不得不說,在很多業(yè)務(wù)中,這種模式用得真的很香
老貓的設(shè)計(jì)模式專欄已經(jīng)偷偷發(fā)車了。不甘愿做crud boy?看了好幾遍的設(shè)計(jì)模式還記不住?那就不要刻意記了,跟上老貓的步伐,在一個(gè)個(gè)有趣的職場故事中領(lǐng)悟設(shè)計(jì)模式的精髓吧。還等什么?趕緊上車吧。
故事
“不能再寫if else來拓展當(dāng)前系統(tǒng)了,現(xiàn)在已經(jīng)有三個(gè)支付場景了......”工位上,小貓看著電腦,撓著頭。
就在剛剛,小貓接到了一個(gè)新需求,需要和客戶公司打通資產(chǎn),形成資產(chǎn)聯(lián)動(dòng)。說白了就是需要定制化對(duì)接客戶公司的支付資產(chǎn)體系。除了這次接到的之外。前面其實(shí)已經(jīng)對(duì)接了三家了。由于每家對(duì)接規(guī)范都不一樣,歷史對(duì)接的時(shí)候?yàn)榱吮M快上線,都是直接搞個(gè)else的新路由分支,然后去實(shí)現(xiàn)支付,退款。
在小貓看來,就是在堆屎山。牽一發(fā)而動(dòng)全身的感覺真的很不好。由于本次的需求留有的時(shí)間還是相當(dāng)充裕的,所以小貓下定決心,打算利用這次的拓展,將原來不合理的地方用上設(shè)計(jì)模式將其重構(gòu)掉。
深思熟慮很久,小貓下定決心打算用“策略模式”重構(gòu)一番。
聊聊策略模式
說到策略模式,老貓覺得這種設(shè)計(jì)模式在實(shí)際開發(fā)中使用其實(shí)是相當(dāng)頻繁的。老貓工作到現(xiàn)在也在很多業(yè)務(wù)場景中使用過這樣的設(shè)計(jì)模式。例如,上述小貓遇到的第三方支付集成的問題上。另外的還有商城搞活動(dòng),針對(duì)不同的用戶下單行為提供不同的折扣或者返現(xiàn)等活動(dòng)。再例如商城運(yùn)營人員根據(jù)不同的加價(jià)策略去定在售商品的價(jià)格等。
老貓工作十年中,對(duì)接過很多外部企業(yè)或者單位的接口,若業(yè)務(wù)定義一樣,只是接口協(xié)議不同的業(yè)務(wù)其實(shí)往往都可以用到策略模式。提煉一下適用場景如下:
(1)系統(tǒng)中有很多類,而它們的區(qū)別僅僅在于行為不同。
(2)一個(gè)系統(tǒng)需要?jiǎng)討B(tài)地在幾種算法中選擇一種。
在很多業(yè)務(wù)中,這種模式用起來真的很香,既能夠擺脫成堆的“if else”(當(dāng)然關(guān)于 if else的優(yōu)化,又是另外一個(gè)故事了,有興趣的小伙伴可以看看這篇文章【接手了個(gè)項(xiàng)目,被if..else搞懵逼了】),另外寫出來的代碼本身拓展性也會(huì)比較好。
那么我們來看看策略模式,并且基于小貓遇到的場景問題,咱們來擼一下實(shí)現(xiàn)代碼。
策略模式解決多路支付通道問題
在定義支付行為的時(shí)候,我們首先定義出常規(guī)的支付行為,咱們可以用接口interface的形式定義出來,當(dāng)然也可以用abstract類的方式定義出來。這里老貓使用后者來定義。代碼如下:
/**
* @author 公眾號(hào):程序員老貓
*/
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);
}
}
定義一個(gè)標(biāo)準(zhǔn)的支付狀態(tài)類:
/**
* @author 公眾號(hào):程序員老貓
*/
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);
}
}
接下來,咱們來模擬各個(gè)支付渠道,并且咱們能夠知道在不同的支付渠道中,我們當(dāng)前的賬戶余額是多少。咱們就拿用得比較多的微信、支付寶、京東支付等支付渠道來做模擬吧。
支付寶實(shí)現(xiàn),并且賬戶中有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);
}
}
定義好各種單一支付通道之后,其實(shí)我們就要組裝策略了。把上述支付通道,加載到策略路由類中。老貓覺得這個(gè)地方也是策略模式中比較核心的點(diǎn)。
/**
* @author 公眾號(hào):程序員老貓
*/
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";
//初始化的時(shí)候裝載支付行為策略
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());
}
//調(diào)用的時(shí)候路由具體的支付策略
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);
}
}
最終咱們來進(jìn)行測試一下:
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));
}
}
結(jié)果輸出:
歡迎使用支付寶
本次交易金額:245
pay state :[200],支付成功,order detail: 支付金額:245
上述基本就是策略模式的使用了。老貓覺得應(yīng)該還是比較清晰的。咱們簡單看一下最終的調(diào)用類圖:
策略模式類圖
到這里很多小伙伴可能會(huì)問了,上面寫的案例其實(shí)并沒有結(jié)合我們實(shí)際的spring開發(fā)框架去實(shí)現(xiàn)策略模式,日常開發(fā)的過程中我們Java程序員主要用的還是spring框架。那么如果要結(jié)合咱們spring日常開發(fā)框架又是怎么去實(shí)現(xiàn)呢。那么接下來,咱們接著往下看。
SpringBoot下策略模式解決多路支付通道
其實(shí)核心的思想還是上面這幾個(gè)要領(lǐng),老貓?jiān)诖瞬欢嘧稣归_,只是給大家提供一些思路,然后提供一些簡單的日常開發(fā)中使用的截圖給大家參考。支付使用策略模式的核心的思想無非就下面兩個(gè)。
(1)咱們需要不同的支付策略類。
(2)需要有路由支付策略類的路由類。
其實(shí)上面兩個(gè)核心中,比較重要的還是第二點(diǎn),咱們?nèi)绻コ跏蓟呗灶?。在上面案例中,老貓使用的靜態(tài)方法塊來裝載各個(gè)策略方法。在spring中其實(shí)我們可以使用@PostConstruct注解,進(jìn)行service策略的初始化裝載。
如下首先定義一個(gè)標(biāo)準(zhǔn)的支付接口,并且實(shí)現(xiàn)一下:
public interface Payment {
//獲取支付渠道的名稱
String getCode();
PayState doPay(String uid, BigDecimal amount);
}
然后實(shí)現(xiàn)這個(gè)接口,咱們舉一個(gè)例子來說明
@Service
public class JDPay implements Payment {
@Override
public String getCode() {
return "jdPay";
}
@Override
public PayState doPay(String uid, BigDecimal amount) {
return null;
}
}
關(guān)鍵此時(shí)咱們看一下核心加載的地方。
/**
* 程序員老貓
**/
@Service
public class PayStrategy {
@Autowired
private Payment[] payments;
//初始化的時(shí)候裝載支付行為策略
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);
}
}
上述就是結(jié)合spring的核心策略模式的實(shí)現(xiàn)方式,老貓這里沒有展開,但是最精華的部分,老貓覺得已經(jīng)說清楚了。當(dāng)然基于@PostConstruct進(jìn)行策略加載的方式只是一種。大家可以實(shí)現(xiàn)spring自帶的InitializingBean,在 Spring 容器完成 bean 的屬性注入后,會(huì)調(diào)用 afterPropertiesSet() 方法來執(zhí)行初始化邏輯。
總結(jié)
上述主要和大家分享了基于策略模式如何去做支付整合第三方支付的問題。當(dāng)然這只是一個(gè)簡單的案例,其實(shí)很多時(shí)候我們?cè)趯?shí)際的業(yè)務(wù)開發(fā)中很多地方都可以用到這樣一個(gè)模式。在jdk源碼中以及spring源碼中也屢見不鮮。但是策略模式也不是萬能的,存在優(yōu)點(diǎn)的同時(shí)也存在缺點(diǎn)。
優(yōu)點(diǎn):
1、策略模式符合開閉原則。(當(dāng)然有興趣了解設(shè)計(jì)原則的小伙伴歡迎戳【違反這些設(shè)計(jì)原則,系統(tǒng)就等著“腐爛”】)
2、策略模式可以避免使用多重復(fù)的條件語句。例如優(yōu)化if else。之前老貓也寫過類似博文?!窘邮至藗€(gè)項(xiàng)目,被if..else搞懵逼了】
3、使用策略模式可以提高算法的保密性和安全性。
缺點(diǎn):
1、不像適配器模式,策略模式要求客戶端需要知道所有的策略,并且自行決定使用哪類策略。關(guān)于適配器模式,感興趣的小伙伴可以看這里【真香定律!我用這種模式重構(gòu)了第三方登錄】
2、策略類會(huì)越來越多,維護(hù)成本也會(huì)越來越高。