当前位置: 首页 > news >正文

基于多种设计模式重构代码(工厂、模板、策略)

基于多种设计模式重构代码

现状

  系统目前支持三种业务流程,业务A, 业务B,业务C,每个流程有相同的业务逻辑,也包含很多的特性化业务。由于之前业务流程的开发是快速迭代的,而且迭代了很多次,开发同学们应该可以理解,过程中免不了面向过程、CV大法…现在无论是业务发展,还是我们产研团队对于业务的理解,都趋于稳定,是时候该还债了,重构代码,我辈义不容辞!

恰好,新需求来了,需要在原有业务B流程中增加一个业务环节,申请支付:业务发起时扣减对应客户的资产余额。

业务流程A抽象后伪码如下:

public void applyPay(Request request) {//数据查询query();//数据校验doCheck(request);//业务逻辑处理if (request.getFlag()) {//资产校验//扣减资产//生成审批流数据//推送钉钉processA();} else {//资产校验//扣减资产//生成审批流数据//推送钉钉processB();}//数据更新update();}

更巧的是之前业务A中做过类似的业务,而且需求评估的时候产品信誓旦旦的说业务B、C中肯定不会有这个业务。

可以预见的是业务C中将来也会有这个业务。

Don not Repeat yourself,这个逻辑公共化处理是必要的。


模板模式

对于上述业务流程,很容易想到模板模式对代码进行通用性抽象优化:

定义算法步骤骨架,并允许子类对一个或多个步骤进行实现

  • 定义模板流程
  • 定义抽象方法,子类强制实现
  • 如有必要定义钩子方法

模板模式很容易理解。

定义一个接口类

public interface IApplyPayProcessor {void applyPay(ApplyPayRequest request);
}

定义抽象父类

public abstract class AbstractApplyPayProcessor<T> implements IApplyPayProcessor{/*** 申请支付模板方法* @param request*/@Overridepublic void applyPay(ApplyPayRequest request) {//数据查询T t= query(request);//校验check(t);//业务逻辑处理process(t, request);//数据保存save(t);//通用业务逻辑处理sendMsg(t);}private void sendMsg(T t) {//发送业务逻辑完成通知 event、msg......实际代码省略}/*** 根据业务,子类实现* @param request* @return*/public abstract T query(ApplyPayRequest request);/*** 根据业务,子类实现* @param t* @return*/public abstract Boolean check(T t);/*** 根据业务,子类实现* @param t* @param request*/public abstract void process(T t, ApplyPayRequest request);/*** 根据业务,子类实现* @param t*/public abstract void save(T t);
}

子类实现

对于业务A,继承AbstractApplyPayProcessor,并根据自身业务的逻辑,实现相应的方法。

对于业务B、业务C也是类似的代码,只需要实现相应的方法,无需对业务流程进行重新的编排,毕竟父类已经定义了骨架方法。

public class BizAApplyPayProcessor extends AbstractApplyPayProcessor<BizADO>{@Overridepublic BizADO query(ApplyPayRequest request) {return new BizADO();}@Overridepublic Boolean check(BizADO o) {//..子类业务逻辑省略return Boolean.TRUE;}@Overridepublic void process(BizADO o, ApplyPayRequest request) {//..子类业务逻辑省略}@Overridepublic void save(BizADO o) {//..子类业务逻辑省略}
}

工厂模式

经过第一步模板模式优化后,我们该怎么使用呢

通常我们会在service层这样使用:

public void doApplyPay(ApplyPayRequest request) {....其他业务逻辑if (request.getType().equals(A)) {BizAApplyPayProcessor processor = new BizAApplyPayProcessor();processor.applyPay(request);} else if (request.getType().equals(B)) {BizBApplyPayProcessor processor = new BizBApplyPayProcessor();processor.applyPay(request);} else if (request.getType().equals(C)) {BizCApplyPayProcessor processor = new BizCApplyPayProcessor();processor.applyPay(request);}..... 其他业务逻辑}

我们很大概率会按照上述代码的编写方式,先通过if/else判断,然后使用new这个关键字,实例化对应的实体,或者Spring的方式注入对应的方式。

new代表着实例化,意味着我们的代码需要依赖具体的实现,而通常推荐的编程是面向接口而不是面向实现。

当有新的类型,比如业务D被添加进来时,这个service需要修改,if/else逻辑需要改变,不符合开闭原则,设计模式的核心的一点就是封装可变的部分。

可以使用工厂模式if/else封装在单独的类,伪代码如下:

public class BizFactory {public static IApplyPayProcessor getApplyPayProcessor(String bizType) {IApplyPayProcessor processor;if (A) {processor = new BizAApplyPayProcessor();processor.applyPay(request);} else if (B) {processor = new BizBApplyPayProcessor();processor.applyPay(request);} else if (C) {processor = new BizCApplyPayProcessor();processor.applyPay(request);}return processor;}
}//service层使用工厂类直接获取对应的实例
public class ClientService {public void doApplyPay(ApplyPayRequest request) {BizFactory.getApplyPayProcessor(request.getType());}
}

这样做起来看起来只是将代码从service层移动到了一个BizFactory中,问题依然是存在的,有什么好处呢?

  • service层不再随着实现的变化而变化,尽量往单一职责去靠拢,注意我这里说的是尽量,因为往往我们会做一个取舍。
  • BizFactory可以有很多业务工厂,其他业务service以后也不需要变化,只需要变化BizFactory

策略模式

我们可以看到对于工厂本身而言,虽然我们将对象的使用和创建已经分离开来,使用工厂模式来专注于对象的创建,当新增业务类型时,比如业务D时,我们需要修改工厂模式,不符合开闭原则

如何进行进一步优化呢,让工厂模式不随着业务类型的增加而进行修改呢。

答案即是使用策略模式

定义一系列的算法或策略,并将每个算法封装在单独的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。

step1 先定一个枚举BizTypeEnum 用来标识业务类型,也就是策略的集合。

@Getter
public enum BizTypeEnum {A(1, "业务A"),B(2, "业务B"),C(3, "业务C"),D(4, "业务D"),;private int code;private String msg;BizTypeEnum(int code, String msg) {this.code = code;this.msg = msg;}/*** 整形值转换为枚举类** @param value 值* @return 枚举类*/public static BizTypeEnum valueOf(int value) {for (BizTypeEnum anEnum : values()) {if (value == anEnum.getCode()) {return anEnum;}}return null;}
}

step2 定义一个注解,ApplyPay用来标记策略类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplyPay {BizTypeEnum bizType() default BizTypeEnum.A;
}

step3 使用注解@ApplyPay,改造标记策略类,

@ApplyPay(bizType = BizTypeEnum.A)
public class BizAApplyPayProcessor extends AbstractApplyPayProcessor<BizADO>{@Overridepublic BizADO query(ApplyPayRequest request) {return new BizADO();}@Overridepublic Boolean check(BizADO o) {//..子类业务逻辑省略return Boolean.TRUE;}@Overridepublic void process(BizADO o, ApplyPayRequest request) {//..子类业务逻辑省略}@Overridepublic void save(BizADO o) {//..子类业务逻辑省略}
}@ApplyPay(bizType = BizTypeEnum.B)
public class BizBApplyPayProcessor extends AbstractApplyPayProcessor<BiZBDO>{@Overridepublic BiZBDO query(ApplyPayRequest request) {return null;}@Overridepublic Boolean check(BiZBDO biZBDO) {return null;}@Overridepublic void process(BiZBDO biZBDO, ApplyPayRequest request) {}@Overridepublic void save(BiZBDO biZBDO) {}
}

step4 识别加载封装策略类,改造工厂类

可以借助Spring的拓展能力,识别策略类的注解,并将策略实例化后,使用集合保存,当然如果没有使用Spring框架,也可以借助反射等来进行此动作。

public class BizFactory implements ApplicationListener<ContextRefreshedEvent> {private static Map<BizTypeEnum, IApplyPayProcessor> APPLY_PAY_MAP = new ConcurrentHashMap<>(8);public static IApplyPayProcessor getApplyPayProcessor(BizTypeEnum bizType) {return APPLY_PAY_MAP.get(bizType);}@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {ApplicationContext applicationContext = event.getApplicationContext();//申请支付工厂初始化Map<String, Object> beansWithAnno = applicationContext.getBeansWithAnnotation(ApplyPay.class);if (beansWithAnno != null) {beansWithAnno.forEach((key, value) -> {BizTypeEnum bizType = value.getClass().getAnnotation(ApplyPay.class).bizType();APPLY_PAY_MAP.put(bizType, (IApplyPayProcessor) value);});}}
}

使用策略模式改造完之后,显而易见的改造后,不同的业务被封装在不同的类中,工厂模式也无需知晓每个类,只需要根据业务类型加载即可。

每次新增业务类型时,只需要新增策略类即可,无需对servicefactory进行修改。

相关文章:

基于多种设计模式重构代码(工厂、模板、策略)

基于多种设计模式重构代码 现状 系统目前支持三种业务流程&#xff0c;业务A&#xff0c; 业务B&#xff0c;业务C&#xff0c;每个流程有相同的业务逻辑&#xff0c;也包含很多的特性化业务。由于之前业务流程的开发是快速迭代的&#xff0c;而且迭代了很多次&#xff0c;开发…...

boomYouth

上一周实在是过得太颓废了&#xff0c;我感觉还是要把自己的规划做好一下&#xff1a; 周计划 这周截至周四&#xff0c;我可以用vue简单的画完登陆注册的界面并且弄一点预处理&#xff1a; 周一 的话可以把这些都学一下&#xff1a; 父传子&#xff0c;子传父&#xff1a…...

关于这个“这是B站目前讲的最好的【Transformer实战】教程!“视频的目前可以运行的源代码GPU版本

课程链接如下&#xff1a; 2.1认识Transformer架构-part1_哔哩哔哩_bilibili 因为网上可以找到源代码&#xff0c;但是呢&#xff0c;代码似乎有点小错误&#xff0c;我自己改正后&#xff0c;放到了GPU上运行&#xff0c; 代码如下&#xff1a; # 来自https://www.bilibil…...

STM32定时器输入捕获测量高电平时间

STM32定时器输入捕获测量高电平时间 输入捕获测量高电平时间CuebMX配置代码部分 本篇内容要求读者对STM32通用定时器有一点理解&#xff0c;如有不解&#xff0c;请看 夜深人静学32系列15——通用定时器 输入捕获 输入捕获是STM32通用定时器的一种功能&#xff0c;可以捕获特定…...

开源WIFI继电器之硬件电路

一、原理图 源文件 二、原理图说明 1、器件说明 U4&#xff1a;ESP8285模块 U6&#xff1a;触发器 U3&#xff1a;继电器 2、继电器状态检测说明 检测继电器线圈是否通电来判断继电器是否导通&#xff0c;当Q1不导通时&#xff0c;Q1集电极的电压为3.3V&#xff0c;经…...

远程执行ssh脚本

sshpass -p 123456 ssh root10.1.10.18 "/root/start.sh"sshpass: 这是一个工具&#xff0c;用于提供密码给 ssh 命令&#xff0c;以便无需手动输入密码就能通过 SSH 连接到远程服务器。 -p ‘123456’: 这是 sshpass 命令的选项&#xff0c;指定了连接时使用的密码…...

excel导入 Easy Excel

依旧是框架感觉有东西&#xff0c;但是确实是模拟不出来&#xff0c;各种零零散散的件太多了 controller层 ApiOperation(value "导入Excel", notes "导入Excel", httpMethod "POST", response ExcelResponseDTO.class)ApiImplicitParams({…...

html实现图片裁剪处理(附源码)

文章目录 1.设计来源1.1 主界面1.2 裁剪界面 2.效果和源码2.1 动态效果2.2 源代码 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/134455169 html实现图片裁剪处理(附源码)&#xff0c;支持图片放大缩小&#…...

前端语言报错

1. 语法错误&#xff08;Syntax Errors&#xff09; 这是由于代码不符合语法规则而引起的错误&#xff0c;通常在代码编译阶段发生。示例&#xff1a; javascriptCopy code if (x 10 { // 缺少了右括号 // 代码逻辑 } 2. 类型错误&#xff08;Type Errors&#xff09; 这…...

详细讲解什么是观察者模式

观察者模式&#xff08;Observer Pattern&#xff09;是一种行为设计模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象&#xff0c;当主题对象状态发生变化时&#xff0c;所有依赖于它的观察者都会得到通知并自动更新。 该模…...

镭速,克服UDP传输缺点的百倍提速传输软件工具

在网络传输中&#xff0c;我们经常会面临这样的困难&#xff1a;文件太大&#xff0c;传输速度太慢&#xff0c;浪费时间和流量&#xff1b;文件太小&#xff0c;传输速度太快&#xff0c;容易出现丢包和乱序&#xff0c;损害数据的完整性和正确性。这些困难的根本在于传输层协…...

Semi-Supervised Multi-Modal Learning with Balanced Spectral Decomposition

Y是所有模态的表征矩阵&#xff0c; ∑ i 1 d h ( λ i ) \sum_{i1}^dh(\lambda_i) ∑i1d​h(λi​) is the proposed eigenvalue-based objective function,the final similarity matrix W for the multimodal data as a block matrix 辅助信息 作者未提供代码...

3296:【例50.2】 计算书费《信息学奥赛一本通编程启蒙(C++版)》

3296&#xff1a;【例50.2】 计算书费《信息学奥赛一本通编程启蒙&#xff08;C版&#xff09;》 【题目描述】 下面是一个图书的单价表&#xff1a; 1、计算概论 28.9 元/本 2、数据结构与算法 32.7 元/本 3、数字逻辑 45.6 元/本 4、C程序设计教程 78 元/本 5、人工智能…...

统一身份认证平台之SSO建设

前言 上篇说道Passwordless无密码技术&#xff0c;也提到了数字时代密码管理的难度&#xff0c;其实在日常的生活中&#xff0c;很多用户也会因为忘记某些网站的登录密码而烦恼。为了方便记忆&#xff0c;很多人都在不同的站点使用相同的用户名和密码&#xff0c;虽然也可以减少…...

【开题报告】基于SpringBoot的膳食营养健康网站的设计与实现

1.选题背景与意义 基于SpringBoot的膳食营养健康网站的设计与实现是一个具有重要意义的选题。背景和意义主要包括以下几点&#xff1a; &#xff08;1&#xff09;社会健康意识的提升&#xff1a;随着人们健康意识的提高&#xff0c;越来越多的人开始关注自己的饮食营养问题。…...

超五类网线和六类网线的相同点和区别

本文对超五类网线和六类网线的相同点和区别进行了简单介绍&#xff0c;帮助大家区分和建立相应的概念。 相同点&#xff1a; &#xff08;1&#xff09;都是网络跳线&#xff0c;用于连接网络设备。 &#xff08;2&#xff09;网线内部由8根不同颜色的线组成。 区别&#xf…...

Linux--初识和基本的指令(1)

目录 前言 0.什么是操作系统 0.1 搭建 Linux 环境 0.2搭建 Linux 环境小结 1.使用 XShell 远程登录 Linux 1.1关于 Linux 桌面 1.2下载安装 XShell 1.3查看 Linux 主机 ip 1.4XShell 下的复制粘贴 2.Linux下基本指令 2.1 pwd命令 2.2 ls命令 2.3 mkdir指令 2.4 cd…...

万宾科技智能井盖传感器,提升市政井盖健康

市政井盖就是城市里不可或缺的基础设施之一&#xff0c;关于它的监测工作可马虎不得。它承载着保护市民的交通安全以及城市正常运转的重要使命。虽然现在城市化的速度很快&#xff0c;但是传统的市政井盖管理方式变得有些力不从心了。井盖的覆盖范围很广&#xff0c;如果单单依…...

transformer学习资料

一、NLP 自然语言处理 NLP 是机器学习在语言学领域的研究&#xff0c;专注于理解与人类语言相关的一切。NLP 的目标不仅是要理解每个单独的单词含义&#xff0c;而且也要理解这些单词与之相关联的上下文之间的意思。 常见的NLP 任务列表&#xff1a; 对整句的分类&#xff1…...

一起学docker系列之四docker的常用命令--系统操作docker命令及镜像命令

目录 前言1 操作 Docker 的命令1.1 启动 Docker1.2 停止 Docker1.3 重启 Docker1.4 查看 Docker 状态1.5 查看 Docker 所有命令的信息1.6 查看某个命令的帮助信息 2 操作镜像的命令2.1 查看所有镜像2.2 搜索某个镜像2.3 下载某个镜像2.4 查看镜像所占空间2.5 删除镜像2.6 强制删…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

大话软工笔记—需求分析概述

需求分析&#xff0c;就是要对需求调研收集到的资料信息逐个地进行拆分、研究&#xff0c;从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要&#xff0c;后续设计的依据主要来自于需求分析的成果&#xff0c;包括: 项目的目的…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

从零实现富文本编辑器#5-编辑器选区模型的状态结构表达

先前我们总结了浏览器选区模型的交互策略&#xff0c;并且实现了基本的选区操作&#xff0c;还调研了自绘选区的实现。那么相对的&#xff0c;我们还需要设计编辑器的选区表达&#xff0c;也可以称为模型选区。编辑器中应用变更时的操作范围&#xff0c;就是以模型选区为基准来…...

练习(含atoi的模拟实现,自定义类型等练习)

一、结构体大小的计算及位段 &#xff08;结构体大小计算及位段 详解请看&#xff1a;自定义类型&#xff1a;结构体进阶-CSDN博客&#xff09; 1.在32位系统环境&#xff0c;编译选项为4字节对齐&#xff0c;那么sizeof(A)和sizeof(B)是多少&#xff1f; #pragma pack(4)st…...

java 实现excel文件转pdf | 无水印 | 无限制

文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在 GPU 上对图像执行 均值漂移滤波&#xff08;Mean Shift Filtering&#xff09;&#xff0c;用于图像分割或平滑处理。 该函数将输入图像中的…...

LeetCode - 199. 二叉树的右视图

题目 199. 二叉树的右视图 - 力扣&#xff08;LeetCode&#xff09; 思路 右视图是指从树的右侧看&#xff0c;对于每一层&#xff0c;只能看到该层最右边的节点。实现思路是&#xff1a; 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲

文章目录 前言第一部分&#xff1a;体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分&#xff1a;体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...