【SpringBoot】策略和模板模式的思考与实践

news2024/12/26 11:02:40

一、应用场景

之所以会将策略和模板模式放在一起,是因为这两种模式用的最多最广泛,而且基本都是联合使用的。在开始之前,先复习一下模式的定义:

  • 模板模式(Template Pattern)

模板模式是在一个抽象类中定义执行的方法,每个方法中都有一个对应的业务流程模板,它的子类需要按照需要来重写模板流程中的方法,子类只需要对这些基本方法进行实现即可,子类并不需要对模板方法进行实现,这种设计模式也属于行为型模式。

  • 策略模式(Strategy Pattern)

策略模式是指一个策略接口被多个策略实现类所实现,通常会创建一个工厂类获取接口实现 bean,具体使用哪一种根据用户选择的类型来和 Map 里的 key 做匹配,策略模式是一种行为性模式。

不管是策略模式还是模板模式,都是运用了  java 多态这一特点,父类引用指向之类对象,通常情况下,模板模式使用的是抽象类,而策略模式使用的是接口而已。其子类都需要子类重写其父类方法或者实现接口方法,两者都满足开闭原则,使得系统在不影响其它功能的前提下更容易拓展。但是两者又有一些差异,模板模式是一种耦合的模式,策略模式是一种松散的模式。模板模式中,通常只有一个业务方法的入口,策略模式中的接口通常会有多个。

单纯的策略模式和模板模式在实践中应用很少,一般都是两种模式结合起来使用,如下图所示,这里即使用了模板模式的高内聚的业务流程,也是用了策略模式的松散性,相同的内容放在抽象类中进行处理,特有的内容放在具体的实现类里面进行操作。

二、应用实践

在介绍了其应用场景后,在这里将结合实际的业务场景来介绍两种设计模式的合并使用。这里采用的是下单支付的场景,用户下单支付成功后,需要邮件和短信通知用户,并且给用户发积分并发送 MQ。

1、PayTypeEnum 枚举类

@Getter
public enum PayTypeEnum {

    ALIPAY(1, "支付宝支付"),
    WEIXIN(2, "微信支付"),
    UNIPAY(3,"银联支付"),
    ;

    PayTypeEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    private Integer code;
    private String msg;

    public static PayTypeEnum getPayType(Integer code) {
        return Arrays.stream(PayTypeEnum.values()).filter(e -> e.getCode().equals(code)).findFirst().orElse(null);
    }

}

2、PayDto 请求体

@Data
public class PayDto {

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

    /**
     * 支付方式 1 支付宝 2 微信 3 银联
     */
    private Integer payType;

}

3、BaseBusiness 类

首先,我们需要定义一个接口类,如下图所示,定义了业务流程方法,发送邮件以及发送短信等方法。

public interface BaseBusiness {

    /**
     * 查询支付方式
     */
    PayTypeEnum getCode();

    /**
     * 处理业务流程
     */
    Result<String> handleOrderFlow(PayDto pay);

    /**
     * 发送邮件
     */
    boolean sendEmail(PayDto pay);

    /**
     * 发送手机短信
     */
    boolean sendPhone(PayDto pay);

}

4、AbstractAppBusinessTemplate 类

实现接口的模板抽象类,定义了业务的流程顺序,以及抽象的支付方法。同时也实现了发送短信和邮件的方法,还有一个发送消息的方法。

@Slf4j
public abstract class AbstractAppBusinessTemplate implements BaseBusiness {

    /**
     * 模板方法:处理业务流程
     */
    @Override
    public final Result<String> handleOrderFlow(PayDto pay) {
        // step1 支付
        boolean result = doPay(pay);
        if (!result) {
            return Result.failed("支付失败!");
        }

        // step2 发送短信和邮件通知到客户
        sendEmail(pay);
        sendPhone(pay);

        // step3 发送用户积分
        grantUserScore(pay);
        // step4 发送消息
        sendMsgMQ(pay);
        return Result.success("处理成功!");
    }

    // 普通方法
    public void sendMsgMQ(PayDto pay){
        log.info("send mq {}", JSONObject.toJSONString(pay));
    }


    public void grantUserScore(PayDto pay){}

    // 订单支付
    protected abstract boolean doPay(PayDto pay);

    @Override
    public boolean sendEmail(PayDto pay) {
        log.info("send email for order {}", pay.getOrderNo());
        return true;
    }

    @Override
    public boolean sendPhone(PayDto pay) {
        log.info("send phone for order {}", pay.getOrderNo());
        return true;
    }

}

5、AliPayAppBusinessStrategy 类

阿里业务类型实现类,这里实现了支付的方法,以及发送短信和邮件的方法,这里不同的业务可能配置不同的短信发送服务,通用的短信发送在抽象模板进行处理,特有的可以在具体的实现类里面实现。

@Slf4j
@Service
public class AlipayAppBusinessStrategy extends AbstractAppBusinessTemplate {

    @Override
    protected boolean doPay(PayDto pay) {
        try {
            TimeUnit.MILLISECONDS.sleep(RandomUtil.randomInt(200, 1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("支付宝支付业务流程");
        return true;
    }

    @Override
    public boolean sendEmail(PayDto pay) {
        System.out.println("AlipayAppBusinessStrategyImpl, email,开始运行");
        super.sendEmail(pay);
        System.out.println("AlipayAppBusinessStrategyImpl, email,运行完成");
        return true;
    }

    @Override
    public boolean sendPhone(PayDto pay) {
        return super.sendPhone(pay);
    }
}

6、UnionPayAppBusinessStrategy 类

@Slf4j
@Service
public class UnionPayAppBusinessStrategy extends AbstractAppBusinessTemplate {

    @Override
    protected boolean doPay(PayDto pay) {
        try {
            TimeUnit.MILLISECONDS.sleep(RandomUtil.randomInt(200, 1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("银联支付业务流程");
        return true;
    }

    @Override
    public boolean sendEmail(PayDto pay) {
        return super.sendEmail(pay);
    }

    @Override
    public boolean sendPhone(PayDto pay) {
        return super.sendPhone(pay);
    }
}

7、WeixinAppBusinessStrategy 类

@Slf4j
@Service
public class WeixinPayAppBusinessStrategy extends AbstractAppBusinessTemplate {

    @Override
    protected boolean doPay(PayDto pay) {
        try {
            TimeUnit.MILLISECONDS.sleep(RandomUtil.randomInt(200, 1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("微信支付业务流程");
        return true;
    }

    @Override
    public boolean sendEmail(PayDto pay) {
        return super.sendEmail(pay);
    }

    @Override
    public boolean sendPhone(PayDto pay) {
        return super.sendPhone(pay);
    }
}

8、PayAppBusinessFactory 工厂类

PayAppBusinessFactory 工厂类获取接口实现 bean,并存储到 ConcurrentHashMap,通过枚举获取对应的实现 bean

@Component
@Slf4j
public class PayAppBusinessFactory implements ApplicationContextAware {

    public static final ConcurrentHashMap<PayTypeEnum, BaseBusinessService> BASE_BUSINESS_BEAN_MAP = new ConcurrentHashMap<>();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

        log.info("PayAppBusinessFactory 启动开始");
        Map<String, BaseBusinessService> map = applicationContext.getBeansOfType(BaseBusinessService.class);

        map.forEach((key, value) -> BASE_BUSINESS_BEAN_MAP.put(value.getCode(), value));

        log.info("PayAppBusinessFactory 启动完成");
 
    }

    public static <T extends BaseBusinessService> T getTrafficMode(PayTypeEnum code) {
        return (T) BASE_BUSINESS_BEAN_MAP.get(code);
    }

}

9、controller

    @PostMapping(value = "handle2")
    public Result<String> handle2(@RequestBody Pay) {

        BaseBusinessService baseBusinessService = PayAppBusinessFactory.getTrafficMode(PayTypeEnum.getPayType(pay.getPayType()));

        Result<String> result = baseBusinessService.handleOrderFlow(pay);

        log.info("result is {}", JSONObject.toJSONString(result));

        return result;
    }

}

10、测试接口 localhost:8080/testUtils/handle2

入参: 

{
  "orderNo": "test1",
  "payType": 1
}

返回结果:

{
    "code": 200,
    "data": "处理成功!",
    "message": "操作成功"
}

日志打印:

PayAppBusinessFactory 启动开始
PayAppBusinessFactory 启动完成
支付宝支付业务流程
AlipayAppBusinessStrategyImpl, email,开始运行
send email for order test1
AlipayAppBusinessStrategyImpl, email,运行完成
send phone for order test1
send mq {"orderNo":"test1","payType":1}
result is {"code":200,"data":"处理成功!","message":"操作成功"}

三、过程中的一些思考与总结

1、Java 类实现某个接口后,可以不用实现接口中的所有方法

接口中的方法都是抽象的,实际修饰符是 public abstract ,我们平时都省略了。所以抽象类不需要全部实现接口中的方法(可以根据自己需要去实现某些接口),但是抽象类的子类(抽象类除外)一定需要实现父类没有实现的接口及父类中所有的抽象方法,也可以全部实现父类实现的接口及父类中所有的抽象方法(如果子类想要调用父类的方法,则用 super 关键字进行调用父类的方法)。

这里面我测试了一下,现象是:当 AbstractAppBusinessTemplate 实现了 handleOrderFlow() 方法,它的子类可以不需要实现该 handleOrderFlow() 方法,当然也可以去实现,子类如果实现 handleOrderFlow() 后,可以用 super 关键字去调用父类该 handleOrderFlow() 方法。如果不想被子类实现该接口,则在模板类中该 handleOrderFlow() 方法添加 final 关键字

2、sendEmail、sendPhone 方法执行顺序

调用接口,以阿里支付为例,先执行 AlipayAppBusinessStrategy 里的 sendEmail 方法,然后 super 调用父类(AbstractAppBusinessTemplate )的 sendEmail 方法,然后再执行 AlipayAppBusinessStrategy 里的 sendEmail 方法里 super 下面的代码。

意图:因为发邮件,每个支付通道的格式以及内容不一样,但是发送是一样的,各自的邮件内容及格式可以写在 AlipayAppBusinessStrategy 里,然后 AbstractAppBusinessTemplate 里的 sendEmail 方法写发送功能代码,因为这部分是一样的。

3、策略类中实现 baseBusiness 接口可写可不写(implements BaseBusiness)

三个策略类中实现 baseBusiness 接口可写可不写,因为继承了父类,父类实现了 baseBusiness 接口,那三个策略类就要全部实现该 baseBusiness 接口中所有的方法

4、abstract 抽象类特征
  • 子类在继承抽象类后,必须实现抽象类中的抽象方法
  • 抽象类中可以有抽象的方法,也可以有普通方法
  • 如果类中含有抽象的方法,当前类必须为抽象类

5、interface 接口特征

在 java 中,可以使用关键字定义一个接口,一个接口由变量的定义和方法定义两部分组成。

   [public] interface 接口名 {

          [public] [static] [final] 变量;

          [public] [abstract] 方法;

   }

实现接口:要让一个类遵循某组特定的接口需要使用implements 关键字

[public] class 类名 implements Interface1,Interface2,...{
    //实现所有接口中声明的方法
}
  • 实现接口的类,称为实现类
  • 实现类必须实现接口中所有的方法
  • 如果不实现接口的方法,那么该实现类也要为一个抽象类 

四、参考文档

springboot-策略和模板模式的思考与实践

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

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

相关文章

OpenGL 入门(八)— 标准光照模型

标准光照模型 前言颜色创建一个光照场景 标准光照模型环境光照漫反射光照法向量计算漫反射光照完整源码 高光反射完整源码 Gouraud着色 前言 冯氏光照模型(Phong Lighting Model)&#xff1a; 一个通过计算环境光&#xff0c;漫反射&#xff0c;和镜面光分量的值来估计真实光照…

大模型运行成本对比:GPT-3.5/4 vs. 开源托管

在过去的几个月里&#xff0c;生成式人工智能领域出现了许多令人兴奋的新进展。 ChatGPT 于 2022 年底发布&#xff0c;席卷了人工智能世界。 作为回应&#xff0c;各行业开始研究大型语言模型以及如何将其纳入其业务中。 然而&#xff0c;在医疗保健、金融和法律行业等敏感应用…

MATLAB实现高通滤波(附完整代码)

1.MATLAB实现高通滤波器 以下是一个使用MATLAB实现高通滤波器的例子。在这个例子中&#xff0c;我们将设计一个简单的数字高通滤波器&#xff0c;然后将其应用到一个包含低频和高频成分的信号上。 clc;close all;clear all;warning off;%清除变量 rand(seed, 500); randn(s…

算法学习——LeetCode力扣哈希表篇2

算法学习——LeetCode力扣哈希表篇2 454. 四数相加 II 454. 四数相加 II - 力扣&#xff08;LeetCode&#xff09; 描述 给你四个整数数组 nums1、nums2、nums3 和 nums4 &#xff0c;数组长度都是 n &#xff0c;请你计算有多少个元组 (i, j, k, l) 能满足&#xff1a; 0 …

PiflowX新增Apache Beam引擎支持

参考资料&#xff1a; Apache Beam 架构原理及应用实践-腾讯云开发者社区-腾讯云 (tencent.com) 在之前的文章中有介绍过&#xff0c;PiflowX是支持spark和flink计算引擎&#xff0c;其架构图如下所示&#xff1a; 在piflow高度抽象的流水线组件的支持下&#xff0c;我们可以…

kubesphere部署k8s-v1.23.10

功能&#xff1a; &#x1f578; 部署 Kubernetes 集群 &#x1f517; Kubernetes 多集群管理 &#x1f916; Kubernetes DevOps &#x1f50e; 云原生可观测性 &#x1f9e9; 基于 Istio 的微服务治理 &#x1f4bb; 应用商店 &#x1f4a1; Kubernetes 边缘节点管理 &#x1…

SD-WAN:企业网络转型的不可逆趋势

随着SD-WAN的逐渐发展和完善&#xff0c;越来越多的企业开始选择SD-WAN进行网络转型。IDC研究显示&#xff0c;已有47%的企业成功迁移到SD-WAN&#xff0c;另有48%的公司表示&#xff0c;未来两个月内将纷纷投入这一技术的部署。 据Channel Futures报道&#xff0c;一位合作伙伴…

网络请求库axios

一、认识Axios库 为什么选择axios? 功能特点: 在浏览器中发送 XMLHttpRequests 请求在 node.js 中发送 http请求支持 Promise API拦截请求和响应转换请求和响应数据 补充: axios名称的由来? 个人理解没有具体的翻译. axios: ajax i/o system 二、axios发送请求 1.axios请求…

sql求解连续两个以上的空座位

Q&#xff1a;查找电影院所有连续可用的座位。 返回按 seat_id 升序排序 的结果表。 测试用例的生成使得两个以上的座位连续可用。 结果表格式如下所示。 A:我们首先找出所有的空座位&#xff1a;1&#xff0c;3&#xff0c;4&#xff0c;5 按照seat_id排序&#xff08;上面已…

滑动小短剧影视微信小程序源码/带支付收益等模式

仿抖音滑动小短剧影视微信小程序源码&#xff0c;带支付收益等模式、支持无限滑动&#xff1b;高性能滑动、预加载、视频预览&#xff0c;支持剧情介绍&#xff0c;集合壁纸另外仿抖音滑动效果&#xff1b;支持会员模式&#xff0c;支持用户单独购买等等多功能。 丰富的后台设…

Deepin系统安装x11vnc远程桌面工具实现无公网ip访问本地桌面

文章目录 1. 安装x11vnc2. 本地远程连接测试3. Deepin安装Cpolar4. 配置公网远程地址5. 公网远程连接Deepin桌面6. 固定连接公网地址7. 固定公网地址连接测试 x11vnc是一种在Linux系统中实现远程桌面控制的工具&#xff0c;它的原理是通过X Window系统的协议来实现远程桌面的展…

2024年2月CCF-全国精英算法大赛题目

第一次参加这种比赛&#xff0c;虽然是c类赛事&#xff0c;但是是ccf主办的&#xff0c;难度还是有点的&#xff0c;主要是前面签到题主要是思想&#xff0c;后面的题目难度太高&#xff0c;身为力扣只刷了一百多道题目的我解决不了&#xff0c;这几道我只做了B,C题,E题超时了&…

html5 audio video

DOMException: play() failed because the user didn‘t interact with the document first.-CSDN博客 不可用&#xff1a; 可用&#xff1a; Google Chrome Close AutoUpdate-CSDN博客

rclone基础命令解析及实战

rclone命令解析及实战 1 rclone介绍&#xff1a;远程同步工具 rclone是一个开源的远程数据同步工具&#xff0c;由Golang编写&#xff0c;旨在在不同平台的文件系统和多种类型的对象存储产品之间提供数据同步功能。 它支持超过 40 种不同的云存储服务&#xff0c;包括 Amazon S…

【代码随想录23】39.组合总和 40.组合总和II 131.分割回文串

目录 39.组合总和题目描述参考代码 40.组合总和II题目描述参考代码 131.分割回文串题目描述参考代码 39.组合总和 题目描述 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 …

RCS系统之:实时获取机器人的摄像头信息

如何获取摄像头信息呢&#xff1f; 一般人都会想到使用比较流行的RSTP,SMTP或者WebRTC等技术。虽然这些技术比较成熟&#xff0c;使用起来也方便&#xff0c;如果只是一个机器人还好&#xff0c;但是十几上百台机器人的时候&#xff0c;那么将会使内网的数据流量造成非常大的压…

Golang 学习(一)基础知识

面向对象 Golang 也支持面向对象编程(OOP)&#xff0c;但是和传统的面向对象编程有区别&#xff0c;并不是纯粹的面向对象语言。 Golang 没有类(class)&#xff0c;Go 语言的结构体(struct)和其它编程语言的类(class)有同等的地位&#xff0c;Golang 是基于 struct 来实现 OOP…

源码梳理(3)MybatisPlus启动流程

文章目录 1&#xff0c;MybatisPlus的使用示例2&#xff0c;BaseMapper方法的执行2,1 MybatisMapperProxy代理对象2.2 InvocationHandler接口&#xff08;JDK动态代理&#xff09;2.3 MapperMethodInvoker接口2.4 MybatisMapperMethod 3&#xff0c;SqlSession的执行流程3.1 Sq…

AUTOSAR内存篇 -EEPROM Abstraction(EA)

文章目录 功能介绍一般行为寻址机制和分段地址计算擦/写次数限制“立即” 数据的处理管理块一致性信息总结本文介绍关于EEPROM Abstraction相关的内容。下图所示为内存硬件抽象层的模块架构图。 EEPROM抽象(EA)从器件特定的寻址方案和分段中抽象出来,并为上层提供虚拟寻址方…

100000行级别数据的 Excel 导入优化之路

项目中有一个 Excel 导入的需求&#xff1a;缴费记录导入 由实施 / 用户 将别的系统的数据填入我们系统中的 Excel 模板&#xff0c;应用将文件内容读取、校对、转换之后产生欠费数据、票据、票据详情并存储到数据库中。 在接手之前可能由于之前导入的数据量并不多没有对效率…