首页 > 其他分享 >不得不说,在很多业务中,这种模式用得真的很香

不得不说,在很多业务中,这种模式用得真的很香

时间:2024-04-26 09:34:18浏览次数:24  
标签:很香 return String 老猫 模式 支付 public 不得不 策略

故事

“不能在写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、策略类会越来越多,维护成本也会越来越高。

标签:很香,return,String,老猫,模式,支付,public,不得不,策略
From: https://www.cnblogs.com/kdaddy/p/18159254

相关文章

  • ECS(Entity-Component-System)模式
    前言:在使用CocosCreator开发弹幕游戏的过程中,由于项目中出现的单位过多,导致项目的性能并不是特别理想,当时研究这个问题的时候看到用ECS可以解决这方面的问题,所以研究后将其应用进项目中,实践后发现ECS这种模式要去unity下插件配合使用效果才大,在cocos上效果甚微;不过上线后勉强还......
  • Linux:VMware切换"仅主机模式"并配置静态IP
    配置网络编辑器点击“编辑”->“虚拟网络编辑器”没有仅主机模式的话,可以通过“添加网络”进行新增网络配置。更改虚拟机网路模式右键“创建的虚拟就”->“设置”登录虚拟机配置静态IP切换目录到“/etc/sysconfig/network-scripts/”修改“if-ens33”文件TYPE=Ethern......
  • 记录一次责任链设计模式使用低级错误
    记录一次责任链设计模式使用低级错误目录记录一次责任链设计模式使用低级错误背景流程发现问题解决方案总结背景提供一个服务支持语音转写成文本,以及历史转写备份数据的简单服务。提供一个接口批量上传,一次最大1000条(分表)落库之后同时发送到消息队列并更新数据状态消费......
  • 前端面试题 - 在HTML5中DOCTYPE的作用是什么?标准与兼容模式(混杂模式)各有什么区别?
    #前端面试题-在HTML5中DOCTYPE的作用是什么?标准与兼容模式(混杂模式)各有什么区别?DOCTYPE(文档类型声明)是一种在HTML文档中使用的标记,用于告诉浏览器使用哪个HTML版本解析文档。它的作用是确保浏览器正确地渲染和显示网页内容。标准模式(严格模式)和兼容模式(混杂模式)是浏览器根......
  • C#实现单例模式的几种方法
    C#实现单例模式的几种方法    C#中readonly的理解与使用   Readonly(C#参考)介绍单例模式是软件工程学中最富盛名的设计模式之一。从本质上看,单例模式只允许被其自身实例化一次,且向外部提供了一个访问该实例的接口。通常来说,单例对象进行实例化时一般不带参数,因为......
  • 设计模式-状态模式在Java中的使用示例-信用卡业务系统
    场景在软件系统中,有些对象也像水一样具有多种状态,这些状态在某些情况下能够相互转换,而且对象在不同的状态下也将具有不同的行为。为了更好地对这些具有多种状态的对象进行设计,我们可以使用一种被称之为状态模式的设计模式。状态模式用于解决系统中复杂对象的状态转换以及不同状......
  • git进入vim模式的处理
    1、当gitcommit没-m输入说明时,我们会进入vim模式,在此模式下可以进行提交说明;2、输入i,进入insert输入模式,输入自己提交内容的说明;3、输入完后,按ESC,下方insert消失;4、输入":",再输wq(写权限及退出),回车,这就完成了,回到命令输入界面补充:按Esc进入普通模式输入:q退出编辑器(若......
  • Docker(十四)-Docker四种网络模式
    Docker安装时会自动在host上创建三个网络,我们可用 dockernetworkls 命令查看:none模式,使用--net=none指定,该模式关闭了容器的网络功能。host模式,使用--net=host指定,容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。bridge模式,使用--net=bridge指定......
  • 关于引脚复用,不得不提的GPIO_PinSource 和GPIO_Pin
    调整前的代码GPIO_PinAFConfig(GPIOA,GPIO_Pin_9,GPIO_AF_4);GPIO_PinAFConfig(GPIOA,GPIO_Pin_10,GPIO_AF_4);调整后的代码GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_4);GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_4);  不难看出,该工程师调整的就是上面红色参......
  • 使用 Gradio 的“热重载”模式快速开发 AI 应用
    在这篇文章中,我将展示如何利用Gradio的热重载模式快速构建一个功能齐全的AI应用。但在进入正题之前,让我们先了解一下什么是重载模式以及Gradio为什么要采用自定义的自动重载逻辑。如果您已熟悉Gradio并急于开始构建,请直接跳转到第三部分构建文档分析应用。重载模式具体是......