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

一、应用场景

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

  • 模板模式(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 {/*** 模板方法:处理业务流程*/@Overridepublic 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);@Overridepublic boolean sendEmail(PayDto pay) {log.info("send email for order {}", pay.getOrderNo());return true;}@Overridepublic boolean sendPhone(PayDto pay) {log.info("send phone for order {}", pay.getOrderNo());return true;}}

5、AliPayAppBusinessStrategy 类

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

@Slf4j
@Service
public class AlipayAppBusinessStrategy extends AbstractAppBusinessTemplate {@Overrideprotected boolean doPay(PayDto pay) {try {TimeUnit.MILLISECONDS.sleep(RandomUtil.randomInt(200, 1000));} catch (InterruptedException e) {e.printStackTrace();}log.info("支付宝支付业务流程");return true;}@Overridepublic boolean sendEmail(PayDto pay) {System.out.println("AlipayAppBusinessStrategyImpl, email,开始运行");super.sendEmail(pay);System.out.println("AlipayAppBusinessStrategyImpl, email,运行完成");return true;}@Overridepublic boolean sendPhone(PayDto pay) {return super.sendPhone(pay);}
}

6、UnionPayAppBusinessStrategy 类

@Slf4j
@Service
public class UnionPayAppBusinessStrategy extends AbstractAppBusinessTemplate {@Overrideprotected boolean doPay(PayDto pay) {try {TimeUnit.MILLISECONDS.sleep(RandomUtil.randomInt(200, 1000));} catch (InterruptedException e) {e.printStackTrace();}log.info("银联支付业务流程");return true;}@Overridepublic boolean sendEmail(PayDto pay) {return super.sendEmail(pay);}@Overridepublic boolean sendPhone(PayDto pay) {return super.sendPhone(pay);}
}

7、WeixinAppBusinessStrategy 类

@Slf4j
@Service
public class WeixinPayAppBusinessStrategy extends AbstractAppBusinessTemplate {@Overrideprotected boolean doPay(PayDto pay) {try {TimeUnit.MILLISECONDS.sleep(RandomUtil.randomInt(200, 1000));} catch (InterruptedException e) {e.printStackTrace();}log.info("微信支付业务流程");return true;}@Overridepublic boolean sendEmail(PayDto pay) {return super.sendEmail(pay);}@Overridepublic 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<>();@Overridepublic 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.mzph.cn/news/674263.shtml

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

相关文章

python+flask人口普查数据的应用研究及实现django

作为一款人口普查数据的应用研究及实现&#xff0c;面向的是大多数学者&#xff0c;软件的界面设计简洁清晰&#xff0c;用户可轻松掌握使用技巧。在调查之后&#xff0c;获得用户以下需求&#xff1a; &#xff08;1&#xff09;用户注册登录后&#xff0c;可进入系统解锁更多…

C#,栅栏油漆算法(Painting Fence Algorithm)的源代码

1 刷油漆问题 给定一个有n根柱子和k种颜色的围栏&#xff0c;找出油漆围栏的方法&#xff0c;使最多两个相邻的柱子具有相同的颜色。因为答案可以是大的&#xff0c;所以返回10^97的模。 计算结果&#xff1a; 2 栅栏油漆算法的源程序 using System; namespace Legalsoft.Tr…

Jetpack Compose之进度条介绍(ProgressIndicator)

JetPack Compose系列&#xff08;12&#xff09;—进度条介绍 Compose自带进度条控件有两个&#xff0c;分别是&#xff1a;CircularProgressIndicator&#xff08;圆形进度条&#xff09;和LinearProgressIndicator&#xff08;线性进度条&#xff09;。 CircularProgressIn…

Codeforces Round 923 (Div. 3)D. Find the Different Ones! 双指针

Problem - D - Codeforces 双指针O(2n)&#xff0c;其实本场B题我写的是O(26n)&#xff0c;结果这道题不敢想哈哈。 题意就是给你一串数字&#xff0c;然后问区间(l , r)内是否有不同的数字&#xff0c;输出任意一组下标即可&#xff0c;没有就输出 -1 -1 所以我们看l 在 l1~…

【51单片机】实现一个动静态数码管显示项目(超全详解&代码&图示)(5)

前言 大家好吖&#xff0c;欢迎来到 YY 滴单片机 系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过单片机的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY…

Redis篇之集群

一、主从复制 1.实现主从作用 单节点Redis的并发能力是有上限的&#xff0c;要进一步提高Redis的并发能力&#xff0c;就需要搭建主从集群&#xff0c;实现读写分离。主节点用来写的操作&#xff0c;从节点用来读操作&#xff0c;并且主节点发生写操作后&#xff0c;会把数据同…

Web后端开发:登录认证案例

登录功能 需求分析 在登录界面中&#xff0c;输入用户的用户名以及密码&#xff0c;然后点击 “登录” &#xff0c;服务端判断用户输入的用户名和密码是否都正确。如果正确&#xff0c;则返回成功结果&#xff0c;前端跳转至系统首页面&#xff1b;否则报错&#xff0c;停留在…

从零开始手写mmo游戏从框架到爆炸(十)— 集成springboot-jpa与用户表

导航&#xff1a;从零开始手写mmo游戏从框架到爆炸&#xff08;零&#xff09;—— 导航-CSDN博客 集成springboot-jpa&#xff0c;不用mybatis框架一个是方便对接不同的数据源。第二个目前规划的游戏内容可能对数据库的依赖不是很大&#xff0c;jpa应该肯定能满足要求了…

Unity类银河恶魔城学习记录4-1,4-2 Attack Logic,Collider‘s collision excepetion源代码 P54 p55

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili Entity.cs using System.Collections; using System.Collections.Generic; u…

深入探索 Express.js 的高级特性

引言 Express.js 是一个基于 Node.js 平台的 Web 开发框架&#xff0c;旨在提供一种简单、易于使用的方式来创建 Web 应用程序。由于其灵活性和可扩展性&#xff0c;它已经成为了 Node.js 社区最受欢迎的框架之一。在本文中&#xff0c;我们将重点介绍 Express.js 的高级特性&…

Flink从入门到实践(一):Flink入门、Flink部署

文章目录 系列文章索引一、快速上手1、导包2、求词频demo&#xff08;1&#xff09;要读取的数据&#xff08;2&#xff09;demo1&#xff1a;批处理&#xff08;离线处理&#xff09;&#xff08;3&#xff09;demo2 - lambda优化&#xff1a;批处理&#xff08;离线处理&…

【Python基础】案例分析:电影分析

电影分析 项目背景&#xff1a; 数据集介绍&#xff1a;movie_lens数据集是一个电影信息&#xff0c;电影评分的数据集&#xff0c;可以用来做推荐系统的数据集需求&#xff1a;对电影发展&#xff0c;类型&#xff0c;评分等做统计分析。目标&#xff1a;巩固pandas相关知识…

Layui 表格组件 头部工具栏 筛选列 加入全选和全不选的功能

Layui 表格组件 头部工具栏 筛选列 加入全选和全不选的功能 问题 前端使用Layui表格组件展示后台数据&#xff0c;因数据中涉及字段较多&#xff0c;因此加入了组件中固有的控制表格列隐藏显示的功能。奈何客户希望再此基础上&#xff0c;加入“全选”和“全不选”的功能&…

【动态规划】【前缀和】【C++算法】LCP 57. 打地鼠

作者推荐 视频算法专题 本文涉及知识点 动态规划汇总 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 LCP 57. 打地鼠 勇者面前有一个大小为3*3 的打地鼠游戏机&#xff0c;地鼠将随机出现在各个位置&#xff0c;moles[i] [t,x,y] 表…

Stable Diffusion 模型下载:Samaritan 3d Cartoon SDXL(撒玛利亚人 3d 卡通 SDXL)

文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十 下载地址 模型介绍 由“PromptSharingSamaritan”创作的撒玛利亚人 3d 卡通类型的大模型&#xff0c;该模型的基础模型为 SDXL 1.0。 条目内容类型大模型基础模型SDXL 1.0来源CIVITA…

2024.2.7

#include<stdio.h> #include<string.h> #include<stdlib.h> typedef char datatype;typedef struct node {//数据域datatype data;//指针域&#xff1a;左struct node *lchild;//指针域&#xff1a;右struct node *rchild; }*btree;//创建节点 btree creat_n…

嵌入式中轻松识别STM32单片机是否跑飞方法

单片机项目偶尔经常出现异常&#xff0c;不知道是程序跑飞了&#xff0c;还是进入某个死循环了&#xff1f; 因为发生概率比较低&#xff0c;也没有规律&#xff0c;所以没办法在线调试查找问题。 结合这个问题&#xff0c;给大家分享一下用ST-LINK Utility识别单片机程序是否…

python-可视化篇-pyecharts库-气候堆叠图

准备 代码 # codingutf-8 # 代码文件&#xff1a;code/chapter10/10.3.py # 3D柱状图import randomfrom pyecharts import options as opts from pyecharts.charts import Bar3D# 生成测试数据 data [[x, y, random.randint(10, 40)] for y in range(7) for x in range(24)]…

Git中为常用指令配置别名

目录 1 前言 2 具体操作 2.1 创建.bashrc文件 2.2 添加指令 2.3 使其生效 2.4 测试 1 前言 在Git中有一些常用指令比较长&#xff0c;当我们直接输入&#xff0c;不仅费时费力&#xff0c;还容易出错。这时候&#xff0c;如果能给其取个简短的别名&#xff0c;那么事情就…

电力负荷预测 | 电力系统负荷预测模型(Python线性回归、随机森林、支持向量机、BP神经网络、GRU、LSTM)

文章目录 效果一览文章概述源码设计参考资料效果一览 文章概述 电力系统负荷预测模型(Python线性回归、随机森林、支持向量机、BP神经网络、GRU、LSTM) 所谓预测,就是指通过对事物进行分析及研究,并运用合理的方法探索事物的发展变化规律,对其未来发展做出预先估计和判断。…