手写工作流设计模式,针对常见的工作流步骤流转,减少过多的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);}// 以此类推 ...}}
}

追加内容 回退节点

噢,忘了写这个内容了,其实也简单,只需要在Node上加上节点。

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class ProcessNode implements Serializable {/*** 节点名称*/private String nodeName;/*** 上继节点*/private List<ProcessNode> upNodes;/*** 后继节点*/private List<ProcessNode> nextNodes;/*** 是否为结束节点*/private boolean isEndNode;public ProcessNode(String nodeName) {this.nodeName = nodeName;this.nextNodes = new ArrayList<>();this.isEndNode = false;}
}

至于在那个业务回退,就需要在对应的run调用设置上继节点就行,如有问题及时沟通。

关于优化

可以把那Node初始化放在static里,不过要做好toString重写,否则很容易触发栈溢出。
以上,就是今天的内容。

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

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

相关文章

Linux系统centos7防火墙firewall开放IP及端口命令

CentOS7使用的是firewall防火墙&#xff0c;不再是原来的iptables 防火墙基础命令 1&#xff1a;查看firewall防火墙状态 firewall-cmd --state //或 systemctl status firewalld2&#xff1a;打开防火墙 systemctl start firewalld3&#xff1a;关闭防火墙 systemctl sto…

什么牌子的led台灯质量好?考研必备五款护眼台灯推荐

眼睛更是心灵的窗户&#xff0c;我们通过这扇窗来欣赏这个美好的世界。而如今&#xff0c;近视在儿童中已司空见惯&#xff0c;近视率逐年提高&#xff0c;并且低龄化的现状更加突出。据世界卫生组织的最新研究报告&#xff0c;目前中国近视患者人数多达6亿&#xff0c;其中我国…

20. Matplotlib 数据可视化

目录 1. 简介2. Matplotlib 开发环境2.1 画图2.2 画图接口2.4 线形图2.5 散点图2.6 等高线图2.7 直方图 1. 简介 Matplotlib网址&#xff1a;https://matplotlib.org/ 数据可视化是数据分析中最重要的工作之一。Matploblib是建立在Numpy数组基础上的多平台数据可视化程序库&a…

WPF前端实现人脸扫描动画效果

前言 本章实现的效果主要通过OpacityMask与LinearGradientBrush(径向渐变) 的组合应用来实现。最终实现效果如下: LinearGradientBrush线性渐变画刷 LinearGradientBrush其实很简单,我们只需要关注5个属性,使用这5个属性你就可以完成这个画刷几乎所有的变化。 属性介…

样品实验Placcel230N聚己内酯二元醇PCL说明书

样品实验Placcel230N聚己内酯二元醇PCL说明书 1KG/罐

财报解读:三季度的美国零售,“沃尔玛效应”仍在持续

经济学中常用“沃尔玛效应”来指代“消费者减少消费时&#xff0c;会选择每种类别中价格最低的商品”这一现象。作为全球最大的零售商&#xff0c;沃尔玛一定程度上成为了消费市场的风向标。 近日&#xff0c;沃尔玛发布的2024财年第三季度财报显示&#xff0c;其相较去年同期…

Linux:Ubuntu系统安装软件

本次以安装vim为例 sudo apt-get remove vim //卸载vim sudo apt-get install vim //安装vim sudo apt-cache show vim //获取vim软件信息安装时间较长。 安装完成后&#xff0c;执行下第三条指令&#xff0c;测试下是否安装成功即可。

c#把bitmap格式转换为其他格式图片

增加引用命名空间 using System.Drawing.Imaging; 打开对话框的方式读入bmp格式图片&#xff0c;转换为其他格式。 也可以直接传入图片名称。 OpenFileDialog ofd new OpenFileDialog();ofd.Title "打开对话框";ofd.InitialDirectory "D:/";ofd.Filt…

ASM字节码操作类库(打开java语言世界通往字节码世界的大门) | 京东云技术团队

前言&#xff1a;授人以鱼不如授人以渔&#xff0c;应用asm的文章有很多&#xff0c;简单demo的也很多&#xff0c;那么ASM都具备哪些能力呢&#xff1f;如何去学习编写ASM代码呢&#xff1f;什么样的情景需要用到ASM呢&#xff1f;让我们带着这些问题阅读这篇文章吧。 这里由…

linux服务器安装gitlab

一、安装gitlab sudo yum install curl policycoreutils-python openssh-server openssh-clients sudo systemctl enable sshd sudo systemctl start sshd sudo firewall-cmd --permanent --add-servicehttp curl https://packages.gitlab.com/install/repositories/gitla…

Golang并发模型:Goroutine 与 Channel 初探

文章目录 goroutinegoexit() channel缓冲closerangeselect goroutine goroutine 是 Go 语言中的一种轻量级线程&#xff08;lightweight thread&#xff09;&#xff0c;由 Go 运行时环境管理。与传统的线程相比&#xff0c;goroutine 的创建和销毁的开销很小&#xff0c;可以…

sap系统连接其它系统

本文来自博客园&#xff0c;作者&#xff1a;Lovemywx2&#xff0c;转载请注明原文链接&#xff1a;https://www.cnblogs.com/1187163927ch/p/8669859.html JAVA连接ORACLE数据库 1&#xff0c;首先需要在Oracle安装完成之后新建一个用户 --新建用户 create user chenh iden…

BOM浏览器对象模型

BOM(Browser Object Model) 浏览器对象模型 操作浏览器api和接口 1.打开链接 返回一个窗口对象 w window.open(url,"_blank",wi…

原神:夏洛蒂是否值得培养?全队瞬抬治疗量不输五星,但缺点也很明显

作为四星冰系治疗角色&#xff0c;夏洛蒂的实战表现可以说相当让人惊喜。不仅有相当有意思的普攻动作以及技能特效&#xff0c;而且她还有治疗和挂冰等功能性。下面就来详细聊聊夏洛蒂是否值得培养。 【治疗量让人惊喜&#xff0c;但也有缺点】 说实话&#xff0c;在使用夏洛蒂…

陶陶摘苹果、跳跃游戏

1. 陶陶摘苹果 题目描述&#xff1a; 陶陶家的院子里有一棵苹果树&#xff0c;每到秋天树上就会结出 10 个苹果。苹果成熟的时候&#xff0c;陶陶就会跑去摘苹果。陶陶有个 30 厘米高的板凳&#xff0c;当她不能直接用手摘到苹果的时候&#xff0c;就会踩到板凳上再试试。 现在…

Spring第三课,Lombok工具包下载,对应图书管理系统列表和登录界面的后端代码,分层思想

目录 一、Lombok工具包下载 二、前后端互联的图书管理系统 规范 三、分层思想 三层架构&#xff1a; 1.表现层 2.业务逻辑层 3.数据层 一、Lombok工具包下载 这个工具包是为了做什么呢&#xff1f; 他是为了不去反复的设置setting and getting 而去产生的工具包 ⚠️工具…

(5h)Unity3D快速入门之Roll-A-Ball游戏开发

DAY1&#xff1a;Unity3D安装 链接 DAY2&#xff1a;构建场景&#xff0c;编写代码 链接 内容&#xff1a;WASD前后左右移动、摄像机跟随 DAY3&#xff1a;待更新 DAY4&#xff1a;待更新 DAY5&#xff1a;待更新

渗透测试|HW蓝队

记录某个对某个钓鱼事件中获取的钓鱼样本进行分析&#xff0c;以及简单的制作学习 样本行为分析 首先看到是 qq 邮箱发来的某个压缩包大概本身是带密码的&#xff0c;反手就丢到虚拟机先看下大概文件&#xff0c;解压后是这样的一个快捷方式 然后打开属性查看快捷方式&#x…

流程不会搭建?集简云上线AI智能创建流程功能,辅助您更简单地创建自动化流程

用户在使用集简云创建流程时&#xff0c;经常会遇到的两个问题&#xff1a; 1. 不知道要如何选择应用动作&#xff0c;和动作的执行顺序&#xff1b; 2. 应用动作设置中的字段匹配&#xff0c;不知道要如何选择对应的字段&#xff1b; 集简云基于大量历史数据积累与自训练AI模…

【001】sg-exam在线考试项目分析-项目结构技术栈

开源项目地址&#xff1a;https://gitee.com/wells2333/sg-exam 系统中服务器端 采用springboot mysql &#xff0c;oss 存储采用 七牛云 或minio 。项目依赖管理采用 gradle. 服务器端模块 &#xff1a; 项目启动入口在&#xff1a; 后台管理系统H5代码&#xff1a; PC端H…