不得不说,在很多业务中,这种模式用得真的很香
# 故事
“不能在写if else来拓展当前系统了,现在已经有三个支付场景了......”工位上,小猫看着电脑,挠着头。
就在刚刚,小猫接到了一个新需求,需要和客户公司打通资产,形成资产联动。说白了就是需要定制化对接客户公司的支付资产体系。除了这次接到的之外。前面其实已经对接了三家了。由于每家对接规范都不一样,历史对接的时候为了尽快上线,都是直接搞个else的新路由分支,然后去实现支付,退款。
在小猫看来,就是在堆屎山。牵一发而动全身的感觉真的很不好。由于本次的需求留有的时间还是相当充裕的,所以小猫下定决心,打算利用这次的拓展,将原来不合理的地方用上设计模式将其重构掉。
深思熟虑很久,小猫下定决心打算用“策略模式”重构一番。
# 聊聊策略模式
说到策略模式,老猫觉得这种设计模式在实际开发中使用其实是相当频繁的。老猫工作到现在也在很多业务场景中使用过这样的设计模式。例如,上述小猫遇到的第三方支付集成的问题上。另外的还有商城搞活动,针对不同的用户下单行为提供不同的折扣或者返现等活动。再例如商城运营人员根据不同的加价策略去定在售商品的价格等。
老猫工作十年中,对接过很多外部企业或者单位的接口,若业务定义一样,只是接口协议不同的业务其实往往都可以用到策略模式。 提炼一下适用场景如下:
(1)系统中有很多类,而它们的区别仅仅在于行为不同。
(2)一个系统需要动态地在几种算法中选择一种。
在很多业务中,这种模式用起来真的很香,既能够摆脱成堆的“if else”(当然关于 if else的优化,又是另外一个故事了,有兴趣的小伙伴可以看看这篇文章【接手了个项目,被if..else搞懵逼了 (opens new window)】),另外写出来的代码本身拓展性也会比较好。
那么我们来看看策略模式,并且基于小猫遇到的场景问题,咱们来撸一下实现代码。
# 策略模式解决多路支付通道问题
在定义支付行为的时候,我们首先定义出常规的支付行为,咱们可以用接口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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
定义一个标准的支付状态类:
/**
* @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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
接下来,咱们来模拟各个支付渠道,并且咱们能够知道在不同的支付渠道中,我们当前的账户余额是多少。咱们就拿用的比较多的微信、支付宝、京东支付等支付渠道来做模拟吧。
支付宝实现,并且账户中有900元:
public class AliPay extends Payment {
@Override
public String getName() {
return "支付宝";
}
@Override
protected BigDecimal queryBalance(String uid) {
return new BigDecimal(900);
}
}
2
3
4
5
6
7
8
9
10
11
微信支付,并且账户中有300元:
public class WxPay extends Payment{
@Override
public String getName() {
return "微信";
}
@Override
protected BigDecimal queryBalance(String uid) {
return new BigDecimal(300);
}
}
2
3
4
5
6
7
8
9
10
11
以此类推,京东支付。
public class JDPay extends Payment{
@Override
public String getName() {
return "京东白条";
}
@Override
protected BigDecimal queryBalance(String uid) {
return new BigDecimal(400);
}
}
2
3
4
5
6
7
8
9
10
11
定义好各种单一支付通道之后,其实我们就要组装策略了。把上述支付通道,加载到策略路由类中。老猫觉得这个地方也是策略模式中比较核心的点。
/**
* @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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
接下来,我们就模拟用户下订单支付行为了,具体如下:
/**
* @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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
最终咱们来进行测试一下:
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));
}
}
2
3
4
5
6
7
结果输出:
欢迎使用支付宝
本次交易金额:245
pay state :[200],支付成功,order detail: 支付金额:245
2
3
上述基本就是策略模式的使用了。老猫觉得应该还是比较清晰的。咱们简单看一下最终的调用类图:
到这里很多小伙伴可能会问了,上面写的案例其实并没有结合我们实际的spring开发框架去实现策略模式,日常开发的过程中我们Java程序员主要用的还是spring框架。那么如果要结合咱们spring日常开发框架又是怎么去实现呢。那么接下来,咱们接着往下看。
# SpringBoot下策略模式解决多路支付通道
其实核心的思想还是上面这几个要领,老猫在此不多做展开,只是给大家提供一些思路,然后提供一些简单的日常开发中使用的截图给大家参考。支付使用策略模式的核心的思想无非就下面两个。
(1)咱们需要不同的支付策略类。
(2)需要有路由支付策略类的路由类。
其实上面两个核心中,比较重要的还是第二点,咱们如果去初始化策略类。在上面案例中,老猫使用的静态方法块来装载各个策略方法。在spring中其实我们可以使用@PostConstruct注解,进行service策略的初始化装载。
如下首先定义一个标准的支付接口,并且实现一下:
public interface Payment {
//获取支付渠道的名称
String getCode();
PayState doPay(String uid, BigDecimal amount);
}
2
3
4
5
6
然后实现这个接口,咱们举一个例子来说明
@Service
public class JDPay implements Payment {
@Override
public String getCode() {
return "jdPay";
}
@Override
public PayState doPay(String uid, BigDecimal amount) {
return null;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
关键此时咱们看一下核心加载的地方。
/**
* 程序员老猫
**/
@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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
上述就是结合spring的核心策略模式的实现方式,老猫这里没有展开,但是最精华的部分,老猫觉得已经说清楚了。当然基于@PostConstruct进行策略加载的方式只是一种。大家可以实现spring自带的InitializingBean,在 Spring 容器完成 bean 的属性注入后,会调用 afterPropertiesSet() 方法来执行初始化逻辑。
# 总结
上述主要和大家分享了基于策略模式如何去做支付整合第三方支付的问题。当然这只是一个简单的案例,其实很多时候我们在实际的业务开发中很多地方都可以用到这样一个模式。在jdk源码中以及spring源码中也屡见不鲜。但是策略模式也不是万能的,存在优点的同时也存在缺点。
优点:
1、策略模式符合开闭原则。(当然有兴趣了解设计原则的小伙伴欢迎戳【违反这些设计原则,系统就等着“腐烂” (opens new window)】)
2、策略模式可以避免使用多重复的条件语句。例如优化if else。当然之前也老猫也写过类似博文。【接手了个项目,被if..else搞懵逼了 (opens new window)】
3、使用策略模式可以提高算法的保密性和安全性。
缺点:
1、不像适配器模式,策略模式要求客户端需要知道所有的策略,并且自行决定使用哪类策略。关于适配器模式,感兴趣的小伙伴可以看这里【真香定律!我用这种模式重构了第三方登录 (opens new window)】
2、策略类会越来越多,维护成本也会越来越高。