基于多种设计模式重构代码(工厂、模板、策略)
基于多种设计模式重构代码
现状
系统目前支持三种业务流程,业务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);});}}
}
使用策略模式改造完之后,显而易见的改造后,不同的业务被封装在不同的类中,工厂模式也无需知晓每个类,只需要根据业务类型加载即可。
每次新增业务类型时,只需要新增策略类即可,无需对service和factory进行修改。
相关文章:
基于多种设计模式重构代码(工厂、模板、策略)
基于多种设计模式重构代码 现状 系统目前支持三种业务流程,业务A, 业务B,业务C,每个流程有相同的业务逻辑,也包含很多的特性化业务。由于之前业务流程的开发是快速迭代的,而且迭代了很多次,开发…...
boomYouth
上一周实在是过得太颓废了,我感觉还是要把自己的规划做好一下: 周计划 这周截至周四,我可以用vue简单的画完登陆注册的界面并且弄一点预处理: 周一 的话可以把这些都学一下: 父传子,子传父:…...
关于这个“这是B站目前讲的最好的【Transformer实战】教程!“视频的目前可以运行的源代码GPU版本
课程链接如下: 2.1认识Transformer架构-part1_哔哩哔哩_bilibili 因为网上可以找到源代码,但是呢,代码似乎有点小错误,我自己改正后,放到了GPU上运行, 代码如下: # 来自https://www.bilibil…...
STM32定时器输入捕获测量高电平时间
STM32定时器输入捕获测量高电平时间 输入捕获测量高电平时间CuebMX配置代码部分 本篇内容要求读者对STM32通用定时器有一点理解,如有不解,请看 夜深人静学32系列15——通用定时器 输入捕获 输入捕获是STM32通用定时器的一种功能,可以捕获特定…...
开源WIFI继电器之硬件电路
一、原理图 源文件 二、原理图说明 1、器件说明 U4:ESP8285模块 U6:触发器 U3:继电器 2、继电器状态检测说明 检测继电器线圈是否通电来判断继电器是否导通,当Q1不导通时,Q1集电极的电压为3.3V,经…...
远程执行ssh脚本
sshpass -p 123456 ssh root10.1.10.18 "/root/start.sh"sshpass: 这是一个工具,用于提供密码给 ssh 命令,以便无需手动输入密码就能通过 SSH 连接到远程服务器。 -p ‘123456’: 这是 sshpass 命令的选项,指定了连接时使用的密码…...
excel导入 Easy Excel
依旧是框架感觉有东西,但是确实是模拟不出来,各种零零散散的件太多了 controller层 ApiOperation(value "导入Excel", notes "导入Excel", httpMethod "POST", response ExcelResponseDTO.class)ApiImplicitParams({…...
html实现图片裁剪处理(附源码)
文章目录 1.设计来源1.1 主界面1.2 裁剪界面 2.效果和源码2.1 动态效果2.2 源代码 源码下载 作者:xcLeigh 文章地址:https://blog.csdn.net/weixin_43151418/article/details/134455169 html实现图片裁剪处理(附源码),支持图片放大缩小&#…...
前端语言报错
1. 语法错误(Syntax Errors) 这是由于代码不符合语法规则而引起的错误,通常在代码编译阶段发生。示例: javascriptCopy code if (x 10 { // 缺少了右括号 // 代码逻辑 } 2. 类型错误(Type Errors) 这…...
详细讲解什么是观察者模式
观察者模式(Observer Pattern)是一种行为设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当主题对象状态发生变化时,所有依赖于它的观察者都会得到通知并自动更新。 该模…...
镭速,克服UDP传输缺点的百倍提速传输软件工具
在网络传输中,我们经常会面临这样的困难:文件太大,传输速度太慢,浪费时间和流量;文件太小,传输速度太快,容易出现丢包和乱序,损害数据的完整性和正确性。这些困难的根本在于传输层协…...
Semi-Supervised Multi-Modal Learning with Balanced Spectral Decomposition
Y是所有模态的表征矩阵, ∑ i 1 d h ( λ i ) \sum_{i1}^dh(\lambda_i) ∑i1dh(λ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:【例50.2】 计算书费《信息学奥赛一本通编程启蒙(C版)》 【题目描述】 下面是一个图书的单价表: 1、计算概论 28.9 元/本 2、数据结构与算法 32.7 元/本 3、数字逻辑 45.6 元/本 4、C程序设计教程 78 元/本 5、人工智能…...
统一身份认证平台之SSO建设
前言 上篇说道Passwordless无密码技术,也提到了数字时代密码管理的难度,其实在日常的生活中,很多用户也会因为忘记某些网站的登录密码而烦恼。为了方便记忆,很多人都在不同的站点使用相同的用户名和密码,虽然也可以减少…...
【开题报告】基于SpringBoot的膳食营养健康网站的设计与实现
1.选题背景与意义 基于SpringBoot的膳食营养健康网站的设计与实现是一个具有重要意义的选题。背景和意义主要包括以下几点: (1)社会健康意识的提升:随着人们健康意识的提高,越来越多的人开始关注自己的饮食营养问题。…...
超五类网线和六类网线的相同点和区别
本文对超五类网线和六类网线的相同点和区别进行了简单介绍,帮助大家区分和建立相应的概念。 相同点: (1)都是网络跳线,用于连接网络设备。 (2)网线内部由8根不同颜色的线组成。 区别…...
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…...
万宾科技智能井盖传感器,提升市政井盖健康
市政井盖就是城市里不可或缺的基础设施之一,关于它的监测工作可马虎不得。它承载着保护市民的交通安全以及城市正常运转的重要使命。虽然现在城市化的速度很快,但是传统的市政井盖管理方式变得有些力不从心了。井盖的覆盖范围很广,如果单单依…...
transformer学习资料
一、NLP 自然语言处理 NLP 是机器学习在语言学领域的研究,专注于理解与人类语言相关的一切。NLP 的目标不仅是要理解每个单独的单词含义,而且也要理解这些单词与之相关联的上下文之间的意思。 常见的NLP 任务列表: 对整句的分类࿱…...
一起学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 强制删…...
SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
深入浅出Diffusion模型:从原理到实践的全方位教程
I. 引言:生成式AI的黎明 – Diffusion模型是什么? 近年来,生成式人工智能(Generative AI)领域取得了爆炸性的进展,模型能够根据简单的文本提示创作出逼真的图像、连贯的文本,乃至更多令人惊叹的…...
上位机开发过程中的设计模式体会(1):工厂方法模式、单例模式和生成器模式
简介 在我的 QT/C 开发工作中,合理运用设计模式极大地提高了代码的可维护性和可扩展性。本文将分享我在实际项目中应用的三种创造型模式:工厂方法模式、单例模式和生成器模式。 1. 工厂模式 (Factory Pattern) 应用场景 在我的 QT 项目中曾经有一个需…...
