基于多种设计模式重构代码(工厂、模板、策略)
基于多种设计模式重构代码
现状
系统目前支持三种业务流程,业务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 强制删…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果 g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...
基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)
引言 工欲善其事,必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后,我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集,就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...
苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会
在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...
