【springboot进阶】摆脱 if/else 的高级应用 - 策略模式

news2024/10/6 14:32:16

目录

一、策略模式的介绍

二、策略模式的使用场景

三、策略模式的应用

1、入参和出参类

2、策略接口

3、策略具体实现

4、策略测试

三、一些使用技巧

四、总结


对于一个逻辑相对复杂的功能应用中,难免需要做很多的逻辑判断,需要写一堆的 if/else,更糟糕的情况是里面还会嵌套在大量的 if/else,如果代码没注释,那简直就让人疯掉了。这时候可能考虑用策略模式去处理一些具有相同逻辑的代码,免除代码体中长串的 if/else 

一、策略模式的介绍

可以先看一张类图,看不懂也没关系,笔者当初刚开始接触的时候也是懵的,第三小节会以人话的方式介绍如何应用到实际的问题中,所以同学们可以先有个概念。

这个模式涉及到三个角色:

  ● 环境(Context)角色:持有一个Strategy的引用。

  ● 抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。

  ● 具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。

二、策略模式的使用场景

举个最常见的场景,就是支付方式,一般有支付宝、微信、银行卡等,如果从代码上来看就是以下这样的。

//支付接口
pay(payType, orderNo) {

    if (payType == 微信支付) {
        微信支付处理
    } else if (payType == 支付宝) {
        支付宝支付处理
    } else if (payType == 银行卡) {
        银行卡支付处理
    } else {
        暂不支持的支付方式
    }

}

如果以后需要增加新的支付方式,我们还需要改这里的代码,再加一段 else if 代码。但是这样做,其实违背了面向对象的 2 个基本原则:

        ● 单一责任原则:一个类应该只有一个发生变化的原因

        ● 开闭原则:对扩展开放,对修改关闭

三、策略模式的应用

我们针对上面支付方式的场景,看看一般策略模式是如何应用到其中的。

1、入参和出参类

定义策略接口统一的入参字段和出参字段,这对于所有的支付方式都是一样的。

1)订单支付类

入参类,定义了一个支付订单可能需要的字段,这里只是简单列举了一些,其中要注意 channel 字段,这是我们在使用策略中的重点,下面会介绍。

/**
 * 订单支付
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
@Data
public class PayOrder {

    /**
     * 支付金额,单位元
     */
    private String mete;
    /**
     * 用户手机号
     */
    private String phone;
    /**
     * 支付渠道
     * ALIPAY:支付宝
     * WECHAT:微信
     * CARD:银行卡
     */
    private String channel;

}

2)支付结果类

出参类,定义了订单支付后所需返回的字段,这里也是简单列举了一些。

/**
 * 支付结果
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
@Data
public class PayResult {

    /**
     * 订单号
     */
    private String order;

    /**
     * 支付结果
     * 1:成功
     */
    private Integer code;

}

2、策略接口

对应着第一点说到的策略类图,去定义策略中所涉及到的接口,但是笔者这边在实际使用中会有些不同。

1)支付处理统一入口

这是暴露给外部业务调用的支付接口。

/**
 * 支付处理服务统一入口
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
public interface VendorPaymentService {

    /**
     * 付款
     *
     * @param payOrder
     * @return
     */
    PayResult pay(PayOrder payOrder);

}

2)支付处理服务接口

相当于策略类图中的 Strategy 策略角色。

/**
 * 支付处理服务
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
public interface PaymentHandleService {

    /**
     * 付款
     *
     * @param payOrder
     * @return
     */
    PayResult pay(PayOrder payOrder);

}

3)支付处理上下文

简单来说就是选择哪种具体策略角色来处理,可以看到这里的入参是上面说的 channel 字段,出参则是策略后所具体的策略角色接口实现。

/**
 * 支付处理上下文
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
public interface PaymentContextService {

    /**
     * 获取处理上下文
     *
     * @param channel
     * @return
     */
    PaymentHandleService getContext(String channel);

}

3、策略具体实现

下面,我们要对上面定义的接口分别写出它们的实现类。

1)支付处理服务统一入口

方法中需要持有一个上下文接口,用于根据订单的渠道,获取具体的支付处理类。

/**
 * 支付处理服务统一入口
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
@Service
public class VendorPaymentServiceImpl implements VendorPaymentService {

    @Resource
    PaymentContextService paymentContextService;

    @Override
    public PayResult pay(PayOrder payOrder) {
        //获取订单中的渠道
        String channel = payOrder.getChannel();
        //根据渠道,具体选择所使用的支付处理类
        PaymentHandleService handleService = paymentContextService.getContext(channel);
        //调用该支付处理类的支付方法
        return handleService.pay(payOrder);
    }

}

2)支付处理上下文

这里是策略的核心部分,根据订单的渠道值去找到对应的具体策略支付类。

/**
 * 支付处理上下文
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
@Service
public class PaymentContextServiceImpl implements PaymentContextService {

    /**
     * 自动注入所有具体策略实现类
     */
    @Resource
    List<PaymentHandleService> handleServiceList;

    /**
     * 额外定义一个不支持的渠道支付方式实现类
     */
    @Resource(name = "NonsupportPaymentHandleServiceImpl")
    PaymentHandleService nonsupportService;

    @Override
    public PaymentHandleService getContext(String channel) {

        if (StrUtil.isEmpty(channel)) {
            return nonsupportService;
        }
        
        //策略实现类上都会打上 Payment 注解,并定义支付方式的值,用于适配订单的渠道值
        PaymentHandleService handleService = handleServiceList.stream()
                .filter(f -> StrUtil.equals(channel, f.getClass().getAnnotation(Payment.class).value()))
                .findFirst()
                .orElse(nonsupportService);

        return handleService;
    }

}

支付方法注解类

/**
 * 支付方式注解
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Payment {

    String value() default "";

}

不支持的渠道支付方式实现类

/**
 * 不支持的业务处理实现
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
@Payment("nonsupport")
@Service("NonsupportPaymentHandleServiceImpl")
public class NonsupportPaymentHandleServiceImpl implements PaymentHandleService {

    @Override
    public PayResult pay(PayOrder payOrder) {
        PayResult result = new PayResult();
        result.setCode(-1);
        return result;
    }
}

3)支付方式具体策略类

这里是列举支付宝支付的实现,其他的支付类似,可以看到这里注解 Payment 定义了这个支付的渠道字段为 alipay

/**
 * 支付宝支付处理
 *
 * @Author Liurb
 * @Date 2022/11/26
 */
@Slf4j
@Payment("alipay")
@Service
public class AlipayPaymentHandleServiceImpl implements PaymentHandleService {

    @Override
    public PayResult pay(PayOrder payOrder) {

        PayResult result = new PayResult();
        result.setOrder("alipay_202211261234567890");
        result.setCode(1);

        log.info("支付宝支付处理 订单信息:{} 支付结果:{}", payOrder, result);

        return result;
    }
}

4、策略测试

我们来写一个单元测试类,这里使用支付宝的方式来支付,看看效果。

@SpringBootTest
class SpringbootAdvanceDemoApplicationTests {

    @Resource
    VendorPaymentService vendorPaymentService;

    @Test
    void contextLoads() {

        PayOrder payOrder = new PayOrder();
        payOrder.setChannel("alipay");
        payOrder.setMete("100");
        payOrder.setPhone("123456");
        
        PayResult payResult = vendorPaymentService.pay(payOrder);
        System.out.println(payResult);
    }

}

在上下文实现类中可以看到,这里已经成功注入了我们的策略实现类。

 根据入参的 channel 值,匹配到了支付宝策略实现类。

 成功的跳转到支付宝策略实现类中。

 接下来尝试调整一下channel的值为 wechat 微信方式,看看效果。

 成功跳转到微信策略实现类中。

三、一些使用技巧

很多时候,策略模式还可以嵌套使用,例如上面举例的支付方式,如果我们每种支付上针对一些特定情况可以给用户打折,那么在打折这种策略上面,我们又可以写另外一套策略实现。

四、总结

同学们可能也有感觉到,使用策略模式,我们就要增加了好几个接口和类,其实这也是它的一个缺点之一,每增加一种支付方式(策略),就需要增加一个类,所以它复用的可能性是很少的。

同时,从上面的举例也能看出,策略的入参和出参都是固定的,但是在实际的项目中,往往都是复杂多变的,所以导致入参和出参会多出很多不是共用的字段,这对于后续维护也是一个问题,因为调用方不一定熟知哪些字段是必须的,所以这可能需要有相关的接口文档作为辅助。

但是对于一个大的系统来说,它可能是更利于维护,毕竟可能一种支付方式本身就是一个复杂的功能,如果大家都在一段代码上面写,那肯定是一种灾难。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/37340.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Flink常用Sink(elasticsearch(es)Sink、RedisSink、KafkaSink、MysqlSink、FileSink)

flink输出到es、redis、mysql、kafka、file 文章目录配置pom文件公共实体类KafkaSInkElasticsearchSink(EsSink)RedisSinkMysqlSink(JdbcSink)FileSink自己先准备一下相关环境 配置pom文件 <properties><maven.compiler.source>8</maven.compiler.source>&l…

【概率论与数理统计】第四章知识点复习与习题

思维导图 基础知识 数学期望 定义 数学期望其实很好理解&#xff0c;就是均值&#xff0c;当然这里并不是直接计算样本的均值&#xff0c;而是考虑到样本对应的概率。我们分离散和连续两类来讨论数学期望。 离散型 对随机变量X的分布律为 若级数 绝对收敛&#xff0c;则称该…

BaGet搭建Nuget私仓(window10docker)

文章目录一、搭建背景二、框架简介三、私仓搭建1、环境2、win10上部署2.1安装SDK2.2下载和解压BaGet包2.3运行项目2.4类库项目2.5将包发布到私有Nuget中2.6使用BaGetFirstLib2.7使用密码增加安全性3、Docker上部署3.1创建相关文件3.2拉取镜像3.3运行3.4访问四、结束一、搭建背景…

微服务入门

文章目录一、微服务大概认识二、单体架构架构和分布式架构三、微服务架构特征四、微服务技术对比五、SpringCloud 与 SpringBoot版本兼容关系如下&#xff1a;一、微服务大概认识 二、单体架构架构和分布式架构 单体架构&#xff1a;将业务的所有功能集中在一个项目中开发&…

“加密上海·喜玛拉雅Web3.0数字艺术大展”落幕,AIGC和数字艺术衍生品是最大赢家?...

图片来源&#xff1a;由无界版图 AI 绘画工具生成11月11日&#xff0c;为期一个月的第一届“加密上海喜玛拉雅3eb3.0数字艺术大展”在喜玛拉雅美术馆拉开帷幕。这无疑是当下中国最盛大、最集中的一次数字艺术展览。艺术展吸引了像Soul 、小红书、网易星球、bilibili、酷天下、无…

mysql实战操作总结

1、问题描述 关于mysql操作&#xff0c;记录下&#xff1b; 2、问题说明 1.停止正在执行的sql 数据量太大&#xff0c;数据库没反应&#xff0c;用的navicat&#xff0c;就在查询页面&#xff0c;执行&#xff1a; show processlist;---会显示对应的查询sql找到最前面是id…

vue js实现文件上传压缩优化处理

vue js实现文件上传压缩优化处理 两种方法 &#xff1a; 第1种是借助canvas的封装的文件压缩上传第2种&#xff08;扩展方法&#xff09;使用compressorjs第三方插件实现 目录 vue js实现文件上传压缩优化处理 借助canvas的封装的文件压缩上传 1.新建imgUpload.js 2.全局引…

高清免费壁纸网站推荐

本期内容&#xff0c;为大家整理了6个相当不错的免费壁纸网站&#xff0c;访问量极大、活跃度极高。 无需登录、注册&#xff0c;打开右键就可以下载&#xff0c;而且壁纸图片的尺寸大小&#xff0c;可以选择&#xff0c;从手机、平板、再到电脑壁纸&#xff0c;全部都是高清。…

Windows/Ubuntu安装frida和objection

​Windows/Ubuntu安装frida和objection 1.Windows环境使用管理员权限安装frida,Ubuntu使用普通或Root权限安装均可 https://github.com/frida/frida (1).安装frida(Python2.7.8及以上版本) pip install numpy matplotlib -i https://mirrors.aliyun.com/pypi/simplepip insta…

imx6ull pro BSP 工具链

BSP&#xff0c;Board Support Package&#xff0c;指板级支持包&#xff0c;是构建嵌入式操作系统所 需的引导程序(Bootload)、内核(Kernel)、根文件系统(Rootfs)和工具链 (Toolchain)。 每种开发板的 BSP 都不一样&#xff0c;并且这些源码都非常庞大。我们把这些源码都 放在…

BI-SQL丨JOB

JOB 在SQL Server中&#xff0c;JOB属于常用功能&#xff0c;我们经常需要通过JOB来执行一些定时的作业任务&#xff0c;例如数据备份、存储过程、SSIS任务、SSAS刷新等等。 通常情况下&#xff0c;我们都是在SSMS中对JOB进行创建、删除、维护等任务的。 前置条件 使用JOB功…

基于Mxnet实现实例分割-MaskRCNN【附部分源码】

文章目录前言一、什么是实例分割二、数据集的准备1.数据集标注2.VOC数据集转COCO数据集三、基于Mxnet搭建MaskRCNN1.引入库2.CPU/GPU配置3.获取训练的dataset1.coco数据集2.自定义数据集4.获取类别标签5.模型构建6.数据迭代器7.模型训练1.优化器设置2.loss计算3.acc计算4.循环训…

堆 堆排序 TopK问题

堆一&#xff0c;堆的相关函数接口实现1&#xff0c;堆的初始化2&#xff0c;堆的销毁3&#xff0c;插入4&#xff0c;向上调整5&#xff0c;删除6&#xff0c;向下调整7&#xff0c;建堆8&#xff0c;取堆顶9&#xff0c;判空10&#xff0c;堆的大小二&#xff0c;向上建堆与向…

用DIV+CSS技术设计的鲜花网站(web前端网页制作课作业)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

(人工智能的数学基础)第一章特征向量与矩阵分析——第一节:向量、向量空间和线性相关性

文章目录一&#xff1a;标量和向量&#xff08;1&#xff09;基本概念&#xff08;2&#xff09;坐标系中的向量表示二&#xff1a;向量运算&#xff08;1&#xff09;加减与数乘&#xff08;2&#xff09;向量内积A&#xff1a;为什么需要向量内积B&#xff1a;向量内积C&…

Linux之分区【详细总结】

目录分区介绍分区查看指令lsblk ![请添加图片描述](https://img-blog.csdnimg.cn/d7ea5468d719433ea6ee4ab0eb145770.png)lsblk -f挂载案例分五部分组成 虚拟机添加硬盘 分区 格式化 挂载 设置自动挂载虚拟机增加硬盘查看整个系统磁盘情况查询查看整个目录磁盘占用情况磁盘情况…

初识 MySQL HeatWave

MySQL 作为全球最欢迎的数据库&#xff0c;已在交易场景叱咤风云多年。在 2020 年底&#xff0c;OCI&#xff08;Oracle Cloud Infrastructure&#xff09;推出了一个黑科技插件&#xff0c;它弥补了 MySQL 在分析场景的短板&#xff0c;Oracle 官方称它比 Aurora 快 1400 倍&a…

GIS 分析常用的 7 个地理处理工具

以下这7 个地理处理工具总是在 GIS 大师的热门列表中名列前茅&#xff0c;似乎如我们的精神食粮&#xff0c;像面包和黄油一样。从裁剪到缓冲&#xff0c;您将学习处理GIS 数据的基础知识&#xff0c;以便更好地了解如何将这些 GIS 工具用于实际应用程序。在ArcGIS 和 QGIS等 G…

Gradle学习笔记之第一个Gradle项目

文章目录前言创建gradle项目gradle目录结构gradle常用命令修改maven仓库地址启用init.gradle的方法关于gradle仓库gradle包装器前言 Gradle是Android构建的基本工具&#xff0c;因此作为Android研发&#xff0c;有必要系统地学一学Gradle&#xff0c;环境windows就可以。 创建…

学生个人网页模板 学生个人网页设计作品 简单个人主页成品 个人网页制作 HTML学生个人网站作业设计代做

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…