手写工作流设计模式,针对常见的工作流步骤流转,减少过多的if/else,提升编程思维

需求

这一年下来,写两次工作流流转,总结下经验。
第一次写的时候,只找到用模版设计模式包裹一下,每个方法都做隔离,但是在具体分支实现的时候,if/else 满屏分,而且因为要针对不同情况,重复代码很多,但是if/else的条件又不一样,搞得我没办法用设计模式修改,想过用工厂模式重构。

一是没时间,二是工厂模式和策略模式基本上都用不来,
首先,工厂模式一定是if else分支较多,并且入参明确、固定。
策略模式也是不同的方法,实现不同的业务,入参明确、固定。
它们两者都不适合参数多一个少一个的情况,用起来只能说恶心自己。
并且由于设计模式的方法过多,时常debug需要嵌套跳转好几轮代码,就比较恶心。

这一年,闲下来我都会重构部分重复的代码,比如if else过多用设计模式优化,优化下来的感受是,没感觉可读性有多提高,反而感觉代码可读性变差了,有些案例的设计模式,很多情况没考虑到,比如较多重复代码,直接复用interface default里的方法,给我直观的体验是其他人来看这个代码,不太好理解。

还不如if else来的直接。

反正只要在if else上注释写清楚,管什么可读性。
在这里插入图片描述

设计模式做不到事

举个例子,当有个非常恶心的业务,需要在两层for循环里写if else,continue关键字是你贴心侍卫,常伴汝身。
你必须用continue它,艹,这个东西用设计模式就不合理,只能复用一些代码,放到一个方法里面去,什么两层for循环里,写个四五百行if else,调试都要好几天,我不知道要是业务出现变动,这个代码后面还怎么改。

新奇的思路

再次遇到工作流,吃过一次亏,不能走老路。
我选择网上冲浪,翻阅资料,最终找到一篇好用例子。
什么都没说,直接上项目,擦,一用才知道里面有坑。

原案例

  1. 定义流程节点
    首先定义一个抽象类ProcessNode,表示工作流中的一个节点:
public abstract class ProcessNode {private String nodeName; // 节点名称private List<ProcessNode> nextNodes; // 后继节点private boolean isEndNode; // 是否为结束节点public ProcessNode(String nodeName) {this.nodeName = nodeName;this.nextNodes = new ArrayList<>();this.isEndNode = false;}public String getNodeName() {return nodeName;}public void setNodeName(String nodeName) {this.nodeName = nodeName;}public List<ProcessNode> getNextNodes() {return nextNodes;}public void setNextNodes(List<ProcessNode> nextNodes) {this.nextNodes = nextNodes;}public boolean isEndNode() {return isEndNode;}public void setEndNode(boolean endNode) {isEndNode = endNode;}
}

其中,nodeName表示节点名称,nextNodes表示后继节点,isEndNode表示是否为结束节点。
接着,定义两个子类StartNode和EndNode,分别表示工作流的起始节点和结束节点:

public class StartNode extends ProcessNode {public StartNode() {super("Start");}
}
public class EndNode extends ProcessNode {public EndNode() {super("End");setEndNode(true);}
}
  1. 定义流程实例
    定义一个ProcessInstance类,表示一次工作流的执行实例:
public class ProcessInstance {private ProcessNode currentNode; // 当前节点public ProcessInstance(ProcessNode startNode) {this.currentNode = startNode;}public ProcessNode getCurrentNode() {return currentNode;}public void setCurrentNode(ProcessNode currentNode) {this.currentNode = currentNode;}
}

其中,currentNode表示当前执行到的节点。
执行工作流
定义一个ProcessEngine类,表示工作流引擎。该类包括以下方法:
addNodes:添加节点
run:执行工作流
代码如下:

public class ProcessEngine {private Map<String, ProcessNode> nodes; // 节点列表public ProcessEngine() {this.nodes = new HashMap<>();}/*** 添加节点*/public void addNodes(ProcessNode... processNodes) {for (ProcessNode node : processNodes) {nodes.put(node.getNodeName(), node);}}/*** 执行工作流*/public void run(ProcessInstance instance) {while (!instance.getCurrentNode().isEndNode()) {ProcessNode currentNode = instance.getCurrentNode();List<ProcessNode> nextNodes = currentNode.getNextNodes();if (nextNodes.isEmpty()) {throw new RuntimeException("No next node found.");} else if (nextNodes.size() == 1) {instance.setCurrentNode(nextNodes.get(0));} else {throw new RuntimeException("Multiple next nodes found.");}}}
}

测试
使用以下代码测试上述工作流引擎的功能:

public static void main(String[] args) {ProcessNode startNode = new StartNode();ProcessNode approveNode = new ProcessNode("Approve");ProcessNode endNode = new EndNode();startNode.setNextNodes(Arrays.asList(approveNode));approveNode.setNextNodes(Arrays.asList(endNode));ProcessEngine engine = new ProcessEngine();engine.addNodes(startNode, approveNode, endNode);ProcessInstance instance = new ProcessInstance(startNode);engine.run(instance);System.out.println("流程执行完成。");
}

运行结果为:
流程执行完成。

填坑

这个案例没考虑到每个Node都是一个function,它需要一个执行function,处理业务逻辑。
怎么玩呢?
使用Function<T, R>特性

public class EndNode extends ProcessNode {public EndNode() {super("End");setEndNode(true);System.out.println("执行end的任务");}public Object executeMethod(Integer languageId, Function<Integer, Object> function) {return function.apply(languageId);}
}

用这种方式把参数传递进去,并业务流转。
然后结合模版模式,把每个abstract的function看做Node,这样就能按照工作流一个方法执行完,执行下一个方法。

public abstract class TestTemplate {abstract Object handler(Integer languageId);// ...
}

第二点,这里缺少一个上一个方法流转结束,返回结果参数作为下一个方法的入参,这里没处理好,这样就会导致某个业务节点失败,回退到上一个节点,取不到入参的问题。

两种解决思路

第一种就是这些返回结果参数,一定要做数据库保存,到进入下一个节点,那这个流转入参就可以删除;

第二种
使用全局Map,并且不使用单例模式,bean注入,而是new Object。(这个不建议)

我们把ProcessEngine的Run方法改到template里面来,

public abstract class TestTemplate {abstract Object handler(Integer languageId);// ...public void init(Integer languageId) {ProcessNode startNode = new StartNode();ProcessNode endNode = new EndNode();startNode.setNextNodes(Arrays.asList(endNode));ProcessEngine engine = new ProcessEngine();engine.addNodes(startNode, endNode);ProcessInstance instance = new ProcessInstance(startNode);run(languageId, instance);}/*** 执行工作流*/private void run(Integer languageId, ProcessInstance instance) {String queryJson = StringConstant.Symbol.BLANK;while (!instance.getCurrentNode().isEndNode()) {ProcessNode currentNode = instance.getCurrentNode();List<ProcessNode> nextNodes = currentNode.getNextNodes();if (nextNodes.isEmpty()) {throw new RuntimeException("No next node found.");}if (nextNodes.size() != 1) {throw new RuntimeException("Multiple next nodes found.");}instance.setCurrentNode(nextNodes.get(0));if (currentNode instanceof StartNode) {StartNode startNode = (StartNode) currentNode;Object obj = startNode.executeMethod(languageId, this::handler);queryJson = JacksonUtil.writeJson(obj);} else if (currentNode instanceof EndNode) {EndNode endNode = (EndNode) currentNode;Object params = new Object();if(StringUtils.isNotBlank(queryJson)) {params = JacksonUtil.readJson(queryJson, Object.class);} else {// 从数据库读取上次function的结果参数}Object obj = endNode.executeMethod(params, this::handler);queryJson = JacksonUtil.writeJson(obj);}// 以此类推 ...}}
}

以上,就是今天的内容。

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

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

相关文章

微信小程序实现类似Vue中的computed、watch功能

微信小程序实现类似Vue中的computed、watch功能 构建npm使用 构建npm 创建包管理器 进入小程序后&#xff0c;打开终端&#xff0c;点击顶部“视图” - “终端” 新建终端 使用 npm init -y初始化包管理器&#xff0c;生成一个package.json文件 安装 npm 包 npm install --…

Java Web 实战 21 - 用 Servlet 实现一个Hello World

用 Servlet 来写一个 Hello World~ 一 . 基本部署方式1.1 创建 Servlet 项目1.2 引入依赖1.3 创建目录1.4 编写代码继承 HttpServlet重写 doGet 方法删除 super 方法加上 WebServlet 注解写业务逻辑 1.5 打包1.6 部署1.7 验证1.8 小结 二 . 更方便的部署方式2.1 Smart Tomcat 的…

本地部署 ComfyUI

本地部署 ComfyUI ComfyUI 介绍ComfyUI Github 地址部署 ComfyUI配置模型地址 or 下载模型启动 ComfyUI访问 ComfyUI ComfyUI 介绍 最强大、模块化的稳定扩散 GUI 和后端。 该用户界面将允许您使用基于图形/节点/流程图的界面设计和执行高级稳定扩散管道。 ComfyUI Github 地…

第五天 用Python批量处理Excel文件,实现自动化办公

用Python批量处理Excel文件&#xff0c;实现自动化办公 一、具体需求 有以下N个表&#xff0c;每个表的结构一样&#xff0c;如下&#xff1a; 需要把所有表数据汇总&#xff0c;把每个人的得分、积分分别加起来&#xff0c;然后按总积分排名&#xff0c;总积分一致时&#xff…

小程序可拖拽按钮

你有没有遇到过在页面中有一个固定在某个位置的按钮&#xff0c;永远的挡住了你想要看的区域&#xff1f; 在小程序的列表页面中&#xff0c;常常会有一个提报的入口固定在右下角&#xff0c;如果这个按钮不可拖动的话&#xff0c;可能会挡住下面的事件&#xff0c;让用户操作起…

云端导览,数字互动 | 拓世法宝AI数字人一体机助力全新旅游时代

《中国旅行消费趋势洞察白皮书&#xff08;2023版&#xff09;》显示&#xff0c;消费者旅行习惯已从“到此一游”变为“深度在地”&#xff0c;更强调在旅游中充实自我、学习新知识。 &#xff08;《中国旅行消费趋势洞察白皮书&#xff08;2023版》截图&#xff09; 从这些资…

Springboot整合MybatisPlus及分页功能

1 引入pom <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot</artifactId><version>2.7.14</version> </dependency> <dependency><groupId>com.baomidou</groupId><a…

【Vue】Vue3 配置全局 scss 变量

variables.scss $color: #0c8ce9;vite.config.ts // 全局css变量css: {preprocessorOptions: {scss: {additionalData: import "/styles/variables.scss";,},},},.vue 文件使用

Java大型电商项目——品优购(一)

视频教程&#xff1a;【黑马程序员】Java大型电商项目—品优购【配套源码笔记】_哔哩哔哩_bilibili源码下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1fECz5In_XCB-aW6ed6ZTbA 提取码&#xff1a;27xa 技术选型&#xff1a; 后端框架&#xff1a;SpringSprin…

多功能回馈式交流电子负载的应用

多功能回馈式交流电子负载是用于模拟和测试电源、电池等电子设备的负载工具。它具有多种应用&#xff0c;可以用于测试和评估各种类型的电源&#xff0c;包括直流电源和交流电源。它可以模拟各种负载条件&#xff0c;如恒定电流、恒定电压和恒定功率&#xff0c;以验证电源的性…

小叶子钢琴智能陪练 助力打牢钢琴基础

孩子在练琴过程中&#xff0c;经常会出现错音错节奏&#xff0c;为了能够帮助她更高效的练琴&#xff0c;最近开始使用智能钢琴陪练工具——小叶子钢琴智能陪练。 身边也有很多朋友在用这款应用&#xff0c;它比较知名的功能就是三大练琴模式&#xff0c;也就是识谱模式、提升…

java SpringCloud版本b2b2c鸿鹄云商平台全套解决方案 小程序商城免费搭建

使用技术&#xff1a; Spring CloudSpring BootMybatis微服务服务监控可视化运营 B2B2C平台&#xff1a; 平台管理端(包含自营) 商家平台端(多商户入驻) PC买家端、手机wap/公众号买家端 微服务&#xff08;30个通用微服务如&#xff1a;商品、订单、购物车、个人中心、支…

Ubuntu20.04清理垃圾vscode缓存

使用VM虚拟机安装了Ubuntu系统&#xff0c;主目录空间越来越小&#xff0c;硬盘扩容之后很快又空间不足&#xff0c;甚至出现了开机卡黑屏的情况&#xff0c;这里记录一下解决过程。 1 重新开机进入系统 状态&#xff1a;卡到了开机黑屏状态&#xff0c;左上角有一条小横杠 原…

国外网站文章或网页采集翻译为中文

采集国外网站的文章或网页数据&#xff08;例如英文&#xff0c;西班牙语&#xff0c;法语等&#xff09;&#xff0c;怎么快速批量翻译为中文&#xff1f; 可以使用简数采集器来实现&#xff0c;支持自动翻译&#xff0c;同时翻译为多种语言&#xff08;不仅中文&#xff09;…

Linux实验三:shell程序设计: shell基础

实验目的: 进一步巩固shell程序设计语言基本语法&#xff0c;加深对所学知识理解。 实验要求 1. 四种变量的使用 2. 配置环境变量 3. 元字符和正则表达式 4. 引号 1. 本地变量 $ var1"hello Linux" //定义本地变量var1 $ read var2 //定义本地变量vae…

Linux常用命令——blkid命令

在线Linux命令查询工具 blkid 查看块设备的文件系统类型、LABEL、UUID等信息 补充说明 在Linux下可以使用blkid命令对查询设备上所采用文件系统类型进行查询。blkid主要用来对系统的块设备&#xff08;包括交换分区&#xff09;所使用的文件系统类型、LABEL、UUID等信息进行…

【linux】服务器CPU占用50%,top/htop/ps却看不到异常进程?使用unhide可以查看!

问题描述 htop发现前32个核全被占满了&#xff0c;但是却找不到对应进程号 查杀 安装unhide查看隐藏进程 apt-get install unhideunhide使用 unhide proc果然发现了隐藏进程 杀死隐藏进程 kill -9 [pid]这么多pid号&#xff0c;我这边杀了其中一个&#xff0c;发现CPU…

本地websocket服务端暴露至公网访问【cpolar内网穿透】

本地websocket服务端暴露至公网访问【cpolar内网穿透】 文章目录 本地websocket服务端暴露至公网访问【cpolar内网穿透】1. Java 服务端demo环境2. 在pom文件引入第三包封装的netty框架maven坐标3. 创建服务端,以接口模式调用,方便外部调用4. 启动服务,出现以下信息表示启动成功…

C++设计模式之工厂模式(中)——工厂模式

工厂模式 工厂模式介绍示例示例使用运行结果工厂模式与简单工厂模式区别 工厂模式 工厂模式在简单工厂模式的基础之上进行了改进。当需要生产的产品种类增加&#xff0c;可以通过新增子类工厂来生产&#xff0c;没有破坏程序设计原则中的开放封闭原则。 介绍 工厂模式先抽象…

域控操作五:统一熄屏睡眠时间

直接看图路径&#xff0c;我只设置了熄屏&#xff0c;如果要睡眠就下面那个启用设置时间