基于Spring的枚举类+策略模式设计(以实现多种第三方支付功能为例)

摘要

最近阅读《贯彻设计模式》这本书,里面使用一个更真实的项目来介绍设计模式的使用,相较于其它那些只会以披萨、厨师为例的设计模式书籍是有些进步。但这书有时候为了使用设计模式而强行朝着对应的 UML 图来设计类结构,并且对设计理念缺少讲解,所以也不能说有多优秀,79分的水平。

书中就这部分内容设计,提到使用了:策略模式、门面模式、策略工厂模式、享元模式。但可能真正称得上是设计的内容就两个部分,策略模式和策略工厂模式。但是就书中所写的策略工厂,个人认为有些啰嗦,并且指定全类名,通过反射来获取对象,这种实现不够优雅。个人相信的设计理念就是在实现代码可扩展的前提下,尽可能使用少的类,只开放必要的接口。虽然 Spring 获取 Bean 本质上也是通过反射来创建的,效率并没有提高。但本文设计并不依赖具体框架,基于 Spring 的目的也是和该书一样,为了让案例更接近现实,基于 Spring 既是一种便利,也是一种约束。

本文的设计方案大体总结如下:

  1. 具体的支付策略实现类(支付宝支付、微信支付)和支付策略门面(Facade)共同实现支付接口
  2. 通过枚举类定义支付策略,并实现编号到实现类的映射。由于实现类是交给 Spring 管理,所以只需要实现编号到 beanName 的映射
  3. 在 Facade 中使用 Map 来实现从编号到实现类对象的映射,根据 Bean 的生命周期,在初始化过程中为 Map 赋值。之所以使用 @PostConstruct 注解,是为了让 init 方法作为 private 方法,而 Facade 只需要暴露上层服务真正需要调用的接口方法就行。

基础环境

pom 依赖

<dependency><groupId>com.alipay.sdk</groupId><artifactId>alipay-sdk-java</artifactId><version>4.34.0.ALL</version>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.7.4</version>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><version>2.7.4</version>
</dependency>

常量工具类

/*** 根据阿里开放平台给出的文档,字段为为第三方平台要求*/
public class AliPayConstant {public static final String OUT_TRADE_NO = "out_trade_no";public static final String TOTAL_AMOUNT = "total_amount";public static final String SUBJECT = "subject";public static final String PRODUCT_CODE = "product_code";public static final String FAST_INSTANT_TRADE_PAY = "FAST_INSTANT_TRADE_PAY";
}

实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@ToString(callSuper = true)
@EqualsAndHashCode
@Builder
public class Order {/*** 订单编号*/private String orderNo;/*** 订单金额*/private Double payment;/*** 订单标题*/private String orderTitle;
}

Spring 配置文件、配置属性类和配置类

payout:alibaba:# 沙箱环境的支付宝网关接口url: https://openapi-sandbox.dl.alipaydev.com/gateway.doapp-id:private-key:alipay-public-key:
@ConfigurationProperties("payout.alibaba")
@Data
public class AlipayProperties {private String url;private String appId;private String privateKey;private String alipayPublicKey;// 默认值private String format = "json";private String charset = "UTF-8";private String signType = "RSA2";
}
@Configuration
@EnableConfigurationProperties(AlipayProperties.class)
public class AlipayConfiguration {@Beanpublic AlipayClient alipayClient(AlipayProperties alipayProperties) throws AlipayApiException {AlipayConfig alipayConfig = new AlipayConfig();//设置网关地址alipayConfig.setServerUrl(alipayProperties.getUrl());//设置应用IDalipayConfig.setAppId(alipayProperties.getAppId());//设置应用私钥alipayConfig.setPrivateKey(alipayProperties.getPrivateKey());//设置请求格式,固定值jsonalipayConfig.setFormat(alipayProperties.getFormat());//设置字符集alipayConfig.setCharset(alipayProperties.getCharset());//设置签名类型alipayConfig.setSignType(alipayProperties.getSignType());//设置支付宝公钥alipayConfig.setAlipayPublicKey(alipayProperties.getAlipayPublicKey());//实例化客户端return new DefaultAlipayClient(alipayConfig);}}

Controller 层

/*** 支付接口,由于要回显html页面,因此直接使用@Controller接口*/
@Controller
@RequestMapping("/payout")
@Slf4j
public class PayoutController {@Autowiredprivate PayoutService payoutService;@SneakyThrows@GetMapping("/{payType}")public void payout(HttpServletResponse response,@PathVariable Integer payType) {log.info("支付方式:{}", payType);Order order = Order.builder().orderNo(UUID.randomUUID().toString()).payment(900.0).orderTitle("兰博基尼").build();String payPageForm = payoutService.pay(order, payType);response.setContentType("text/html;charset=utf-8");PrintWriter out = response.getWriter();out.write(payPageForm);out.flush();out.close();log.info("显示支付页面");}@SneakyThrows@GetMapping("/callback")public void callback(HttpServletResponse response) {log.info("回调页面");PrintWriter out = response.getWriter();out.write("Hello World");out.flush();out.close();log.info("写出消息");}
}

Service 层

@Service
public class PayoutService {@Autowiredprivate PayStrategyFacade payStrategyFacade;public String pay(Order order, Integer payType) {return payStrategyFacade.pay(order, payType);}
}

设计模式部分

策略接口

public interface PayStrategy {/*** 调用第三方支付接口** @param order 订单封装* @return 调用成功返回页面信息,即response.getBody();调用失败返回null*/String pay(Order order);
}

策略实现类(支付宝支付、微信支付、银行支付等)

复制支付宝、微信等开放平台的代码内容即可

@Component("aliPay")
public class AliPayStrategyImpl implements PayStrategy {@Autowiredprivate AlipayClient alipayClient;@Overridepublic String pay(Order order) {// 支付金额double payAmount = order.getPayment();// 订单标题String orderTitle = order.getOrderTitle();// 商户订单号String orderNo = order.getOrderNo();// 不同的请求类型构造不同的Request对象AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();request.setNotifyUrl("");request.setReturnUrl("http://localhost:8080/payout/callback");JSONObject bizContent = new JSONObject();// 必传参数// 商户订单号,商家自定义,保持唯一性bizContent.put(AliPayConstant.OUT_TRADE_NO, orderNo);// 支付金额,最小值0 .01 元bizContent.put(AliPayConstant.TOTAL_AMOUNT, payAmount);// 订单标题,不可使用特殊符号bizContent.put(AliPayConstant.SUBJECT, orderTitle);// 电脑网站支付场景固定传值FAST_INSTANT_TRADE_PAYbizContent.put(AliPayConstant.PRODUCT_CODE, AliPayConstant.FAST_INSTANT_TRADE_PAY);request.setBizContent(bizContent.toString());AlipayTradePagePayResponse response;try {response = alipayClient.pageExecute(request);} catch (AlipayApiException e) {throw new RuntimeException(e);}if (response.isSuccess()) {// 在网站上显示支付宝支付页面,让用户扫描支付return response.getBody();}return null;}
}

策略枚举类

枚举所有的策略,由于枚举类对象是静态对象,因此不能够将其直接作为 Spring 容器的 Bean。

最开始枚举类中设计的两个字段分别是 int 类型的 payType 和 PayStrategy 类型的对象,希望通过 ALIBABA(0, new AliPayStrategyImpl()) 的方式来初始化枚举类对象。但是由于 AliPayStrategyImpl 中的 AliClient 是交给了 Spring 容器进行管理,而使用 new 方式得到的对象没有经过 Spring,所以其中的 AliClient 为 null。

同时即使将枚举类交给 Spring 管理,其依赖注入也十分麻烦,通过初始化方法去覆盖类对象中的属性,也需要依赖 beanName,且设计丑陋,没有枚举类的优雅。因此直接在枚举类中负责管理 beanName**(在 Spring 框架中,管理了 beanName,就是管理了 BeanDefinition)**

@Getter
public enum PayStrategyEnum {// TODO: 由于实现类依赖了Spring的自动注入来获取AliClient, 因此直接new AlipayStrategyImpl()不会自动注入AliClientALIBABA(0, "aliPay"),WECHAT(1, "wechatPay"),;private final int payType;/*** 枚举类结合Spring的中介产物,根据迪米特法则,不需要对外开放*/private final String beanName;PayStrategyEnum(Integer payType, String beanName) {this.payType = payType;this.beanName = beanName;}
}

策略门面?策略上下文?策略工厂?

@Component
public class PayStrategyFacade {@Autowiredprivate ApplicationContext applicationContext;// 由于只在初始化的时候进行设置,并发读不存在线程安全问题,因此不需要使用ConcurrentHashMapprivate static final Map<Integer, PayStrategy> PAY_STRATEGIES = new HashMap<>(PayStrategyEnum.values().length);@PostConstructprivate void init() {// 初始化策略for (PayStrategyEnum payStrategyEnum : PayStrategyEnum.values()) {PAY_STRATEGIES.put(payStrategyEnum.getPayType(),applicationContext.getBean(payStrategyEnum.getBeanName(), PayStrategy.class));}}public String pay(Order order, Integer payType) {PayStrategy payStrategy = PAY_STRATEGIES.getOrDefault(payType, null);if (payStrategy == null) {throw new RuntimeException("不支持的支付类型");}return payStrategy.pay(order);}
}

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

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

相关文章

易点易动固定资产集成飞书,实现固定资产的一站式高效管理

在现代商业环境中&#xff0c;固定资产管理对于企业的运营和成功至关重要。然而&#xff0c;传统的资产管理方式往往繁琐、容易出错&#xff0c;并且缺乏实时性和准确性。为了解决这些挑战&#xff0c;易点易动与飞书进行了集成合作&#xff0c;推出了一种全新的解决方案&#…

common-pool的GenericObjectPool源码创建borrowObject方法研读

对象池主要管理对象的池&#xff0c;包含借用&#xff0c;归还&#xff0c;添加对象&#xff0c;校验对象是否有效等管理功能 public T borrowObject(final long borrowMaxWaitMillis) throws Exception {assertOpen();final AbandonedConfig ac this.abandonedConfig;if (ac …

ASP.NET Core面试题之Redis高频问题

&#x1f388;&#x1f388;在.NET后端开发岗位中&#xff0c;如今也少不了、微服务、分布式、高并发高可用相关的面试题&#x1f388;&#x1f388; &#x1f44d;&#x1f44d;本文分享一些整理的Redis高频面试题&#x1f389; &#x1f44d;&#x1f44d;机会都是给有准备…

Springboot访问html页面

目录 1、html页面创建 2、打开application.properties,添加如下配置 3、Controller中的代码 4、测试效果 项目结构如图 1、html页面创建 在原有的项目resouces目录下创建static包,并在static下创建pages,然后在pages包下index.html. index.html内容 <!DOCTYPE html>…

机器学习基础实验(人口收入普查数据探索)

本次挑战中&#xff0c;你需要运用 Pandas 探索数据&#xff0c;并回答有关 Adult 数据集 的几个问题。Adult 数据集是一个关于人口收入普查的数据集&#xff0c;其包含多个特征&#xff0c;目标值为类别类型。 首先&#xff0c;我们加载并预览该数据集。 import warnings i…

打破微软封印面向未来创建.NET Framework4.8工程

摘要&#xff1a; 工程从.NET Framework 4.8升级到.NET 8.0&#xff0c;即使采用官方方案也是很繁琐的一件事情&#xff0c;而且容易出问题。Windows 11内置了.NET Framework 4.8&#xff0c;所以当前的软件需要基于.NET Framework 4.8。但后续微软推出Windows 12&#xff0c;…

海康威视IP网络对讲广播系统命令执行漏洞(CVE-2023-6895)

漏洞介绍 海康威视IP网络对讲广播系统采用领先的IPAudio™技术,将音频信号以数据包形式在局域网和广域网上进行传送,是一套纯数字传输系统。 Hikvision Intercom Broadcasting System 3.0.3_20201113_RELEASE(HIK)版本存在操作系统命令注入漏洞&#xff0c;该漏洞源于文件/ph…

Linux网络编程(一):网络基础(下)

参考引用 UNIX 环境高级编程 (第3版)黑马程序员-Linux 网络编程 1. 协议的概念 1.1 什么是协议 从应用的角度出发&#xff0c;协议可理解为 “规则”&#xff0c;是数据传输和数据解释的规则 假设&#xff0c;A、B双方欲传输文件&#xff0c;规定&#xff1a; 第一次&#xff…

基于vue-cli快速发布vue npm 包

一、编写组件 1. 初始化项目并运行 vue create vue-digital-countnpm run serve2. 组件封装 新建package文件夹 ​ 因为我们可能会封装多个组件&#xff0c;所以在src下面新建一个package文件夹用来存放所有需要上传的组件。 ​ 当然&#xff0c;如果只有一个组件&#xff…

Guava事件总线的应用与最佳实践

第1章&#xff1a;引言 走过路过不要错过&#xff01;今天&#xff0c;小黑带大家深入了解Guava事件总线&#xff08;EventBus&#xff09;。咱们先聊聊&#xff0c;为什么这个东西这么酷&#xff1f;如果你是一名Java开发者&#xff0c;肯定知道&#xff0c;管理复杂的应用程…

JS常用方法

1、reduce()统计 &#xff08;1&#xff09;数组和 计算并返回给定数组 arr 中所有元素的总和 let arr [1,4,3,6,2,6] function sum(){const newArr arr.reduce((pre,item)>{return preitem})console.log(newArr);//22 } sum() 2、filter()过滤器 &#xff08;1&#…

【Kafka集群架构设计原理】

文章目录 一、Kafka的Zookeeper元数据梳理 一、Kafka的Zookeeper元数据梳理 1、zookeeper整体数据 Kafka将状态信息保存在Zookeeper中&#xff0c;这些状态信息记录了每个Kafka的Broker服务与另外的Broker服务 有什么不同。通过这些差异化的功能&#xff0c;共同体现出集群化的…

HarmonyOS:Neural Network Runtime 对接 AI 推理框架开发指导

场景介绍 Neural Network Runtime 作为 AI 推理引擎和加速芯片的桥梁&#xff0c;为 AI 推理引擎提供精简的 Native 接口&#xff0c;满足推理引擎通过加速芯片执行端到端推理的需求。 本文以图 1 展示的 Add 单算子模型为例&#xff0c;介绍 Neural Network Runtime 的开发流…

精通服务器远程管理:全面指南

引言 在当今数字化世界中&#xff0c;IT专业人员和管理员能够远程管理服务器的能力是无价之宝。远程服务器管理不仅提高了效率&#xff0c;而且在无法物理访问服务器的情况下确保了持续的运营。本指南将深入探讨远程管理的不同类型、远程桌面的使用方法&#xff0c;以及如何安全…

一、W5100S/W5500+RP2040之MicroPython开发<静态网络示例>

文章目录 1. 前言2. MicroPython介绍2.1 简介2.2 优点2.3 应用 3. WIZnet以太网芯片4. 静态IP网络设置示例讲解以及使用4.1 程序流程图4.2 测试准备4.3 连接方式4.4 相关代码4.5 烧录验证步骤1&#xff1a;将固件部署到设备步骤2&#xff1a;运行network_install.py程序步骤3&a…

【大数据存储与处理】实验一 HBase 的基本操作

一、实验目的&#xff1a; 1. 掌握 Hbase 创建数据库表及删除数据库表 2. 掌握 Hbase 对数据库表数据的增、删、改、查。 二、实验内容&#xff1a; 1、题目 0&#xff1a;进入 hbase shell 2、题目 1&#xff1a;Hbase 创建数据库表 创建数据库表的命令&#xff1a;create 表…

重塑数字生产力体系,生成式AI将开启云计算未来新十年?

科技云报道原创。 今天我们正身处一个历史的洪流&#xff0c;一个巨变的十字路口。生成式AI让人工智能技术完全破圈&#xff0c;带来了机器学习被大规模采用的历史转折点。 它掀起的新一轮科技革命&#xff0c;远超出我们今天的想象&#xff0c;这意味着一个巨大的历史机遇正…

【Dubbo】默认hession2反序列化机制导致dubbo接口返回HashMap

问题描述 在使用dubbo调用接口的时候&#xff0c;莫名其妙出现java.lang.ClassCastException: java.util.HashMap cannot be cast to xxxx异常经过排查发现&#xff0c;是因为dubbo接口返回的不是xxxx对象&#xff0c;而是HashMap 源码分析 dubbo的反序列化机制默认是hessia…

【扩散模型】8、DALL-E2 | 借助 CLIP 的图文对齐能力来实现文本到图像的生成

文章目录 一、背景二、方法2.1 Decoder2.2 Prior 三、图像控制3.1 Variations3.2 Interpolations3.3 Text Diffs 四、探索 CLIP 的潜在空间五、文本到图像的生成5.1 先验的重要性5.2 人类评价5.3 多样性和保真性的平衡5.3 在 COCO 上对比 论文&#xff1a;DALLE.2 代码&#x…

《网络安全面试总结》--web白盒漏洞问题

网络安全面试题目 Web安全 web白盒漏洞问题 1.JAVA反序列化了解吗&#xff1f;有没有了解过shrio反序列化&#xff1f; Java中的ObjectOutputStream类的writeObject()方法可以实现序列化&#xff0c;其作用把对象转换成字节流&#xff0c;便于保存或者传输&#xff0c;而Ob…