【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.hqwc.cn/news/454664.html

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

相关文章

day21网页布局

文章目录 块元素和行内元素列表标签表格标签媒体元素页面结构分析iframe内联框架 块元素和行内元素 块元素&#xff1a;无论内容多少&#xff0c;该元素独占一行。(p标签、h1~h6…) 行内元素&#xff1a;内容撑开宽度&#xff0c;左右都是行内元素的可以排在一行。&#xff08…

基于CNN卷积网络的MNIST手写数字识别matlab仿真,CNN编程实现不使用matlab工具箱

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 卷积神经网络&#xff08;CNN&#xff09; 4.2 损失函数和优化 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ......................…

外汇天眼:SIX推出了新的SIX参考利率加密货币和SIX实时加密货币指数

全球金融信息提供商SIX今天宣布推出新的SIX参考利率加密货币和SIX实时加密货币指数。新的SIX参考利率加密货币指数和SIX实时加密货币指数涵盖了主要的加密资产比特币&#xff08;BTC&#xff09;和以太坊&#xff08;ETH&#xff09;&#xff0c;为市场及其表现提供了全面的快照…

Goland控制台日志打印错位

现象&#xff1a;Goland控制台打印日志&#xff0c;调整控制台界面大小后偶发性的日志内容错位 原因&#xff1a;未知&#xff08;大概是bug&#xff09; 解决方案&#xff1a; shift shift 进入Registry&#xff0c;取消go.run.process.with.pty勾选即可

《动手学深度学习(PyTorch版)》笔记7.5

注&#xff1a;书中对代码的讲解并不详细&#xff0c;本文对很多细节做了详细注释。另外&#xff0c;书上的源代码是在Jupyter Notebook上运行的&#xff0c;较为分散&#xff0c;本文将代码集中起来&#xff0c;并加以完善&#xff0c;全部用vscode在python 3.9.18下测试通过&…

Qt|实现时间选择小功能

在软件开发过程中&#xff0c;QtDesigner系统给出的控件很多时候都无法满足炫酷的效果&#xff0c;前一段时间需要用Qt实现选择时间的小功能&#xff0c;今天为大家分享一下&#xff01; 首先看一下时间效果吧&#xff01; 如果有需要继续往下看下去哟~ 功能 1&#xff1a;开…

Vue-easy-tree封装及使用

1.使用及安装 下载依赖 npm install wchbrad/vue-easy-tree引入俩种方案 1.在main.js中引入 import VueEasyTree from "wchbrad/vue-easy-tree"; import "wchbrad/vue-easy-tree/src/assets/index.scss" Vue.use(VueEasyTree)2.当前页面引入 import VueEa…

PiflowX新增Apache Beam引擎支持

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

2024第九届国际发酵培养基应用与发展技术论坛会议通知

会议简介 随着科技的不断发展,发酵技术已逐渐成为生物制造中的重要支撑&#xff0c;发酵技术应用领域更加广泛。发酵培养基是影响生物发酵产业技术水平、环境友好程度的重要因素之一&#xff0c;为进一步推动发酵培养基的科学应用&#xff0c;提升发酵培养基的高效、稳定应用&…

电商推荐系统

此篇博客主要记录一下商品推荐系统的主要实现过程。 一、获取用户对商品的偏好值 代码实现 package zb.grms;import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configured; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.Doub…

开发大佬为什么都不喜欢关电脑?

引言 在平时工作中&#xff0c;咱们程序员这一群体往往展现出一些特有的行为习惯&#xff0c;其中之一便是不喜欢频繁地关闭电脑、拒绝关机、长久待机、特别是苹果的机器。 下面从技术分析与用户行为研究的角度出发&#xff0c;将深入探讨程序员倾向于保持电脑开机状态的原因…

数字孪生网络攻防模拟与城市安全演练

在数字化浪潮的推动下&#xff0c;网络攻防模拟和城市安全演练成为维护社会稳定的不可或缺的环节。基于数字孪生技术我们能够在虚拟环境中进行高度真实的网络攻防模拟&#xff0c;为安全专业人员提供实战经验&#xff0c;从而提升应对网络威胁的能力。同时&#xff0c;在城市安…