手写工作流设计模式,针对常见的工作流步骤流转,减少过多的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.hqwc.cn/news/213287.html

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

相关文章

php xml数据转数组两种方式

目录 方法一、可以使用simplexml_load_string()函数将XML数据转换为数组。 方法二、使用PHP内置的DOMDocument类来将XML数据转换为数组的方法 方法一、可以使用simplexml_load_string()函数将XML数据转换为数组。 $xmlData <root><name>John Doe</name>&l…

电脑软件:SmartSystemMenu(窗口置顶工具)介绍

目录 一、软件介绍 二、软件用途 三、安装教程 注意事项 四、功能介绍 五、软件设置 六、软件下载 一、软件介绍 SmartSystemMenu 是一款简单实用的 Windows 窗口增强工具&#xff0c;它可以为窗口的标题栏右键菜单新增 17 个新功能。 二、软件用途 SmartSystemMenu(窗口…

python-opencv在图片中绘制各种图形

python-opencv在图片中绘制各种图形 1.绘制直线 2.绘制矩形 3.绘制圆 4.绘制椭圆 5.绘制多边形 6.嵌入文字 实现代码都在下面了&#xff0c;代码中参数做了简单注释 import copy import math import matplotlib.pyplot as plt import matplotlib as mpl import numpy a…

微服务知识小结

1. SOA、分布式、微服务之间有什么关系和区别&#xff1f; 1.分布式架构指将单体架构中的各个部分拆分&#xff0c;然后部署到不同的机器或进程中去&#xff0c;SOA和微服务基本上都是分布式架构的 2. SOA是一种面向服务的架构&#xff0c;系统的所有服务都注册在总线上&#…

python树的双亲存储结构

这种存储结构是一种顺序存储结构&#xff0c;采用元素形如“[结点值&#xff0c;双亲结点索引]”的列表表示。通常每个结点有唯一的索引(或者伪地址&#xff09;,根结点的索引为0&#xff0c;它没有双亲结点&#xff0c;其双亲结点的索引为-1。例如&#xff0c;所示的树对应的双…

qgis添加postgis数据

左侧浏览器-PostGIS-右键-新建连接 展开-双击即可呈现 可以点击编辑按钮对矢量数据编辑后是直接入库的&#xff0c;因此谨慎使用。

Rust语言入门教程(一) - 简介及Cargo使用

Rust编程入门 为什么学习Rust 我本人是一个DevOps工程师&#xff0c;并不是专职的开发人员&#xff0c;但需要了解各种各样的语言的基本知识和特性&#xff0c;以便在不同的项目中帮助开发人员设计软件架构&#xff0c;部署流程以及进行错误排查和调试。但是对任何新生的优秀…

聚观早报 |一加12正式开启预订;OPPO Reno11系列卖点

【聚观365】11月24日消息 一加12正式开启预订 OPPO Reno11系列卖点 小鹏第三季度营收财报 Claude 2.1 聊天机器人公布 现代汽车将与伦敦大学学院合作 一加12正式开启预订 全新的一加12系列公开亮相已有一段时间&#xff0c;不久前一加官方宣布&#xff0c;该机将于12月4日…

【JavaWeb】HTMLCSSJavaScript

HTML&CSS&JavaScript 文章目录 HTML&CSS&JavaScript一、开发工具及在线帮助文档二、 HTML2.1 HTML&CSS&JavaScript的作用2.2 HTML基础结构2.3 HTML概念词汇解释2.4 HTML的语法规则2.5 常用标签 三、CSS3.1 引入方式3.2 CSS选择器3.3 CSS浮动3.4 CSS定位…

【JavaScript】3.1 项目实践:制作一个简单的网页应用

文章目录 项目需求HTML结构JavaScript逻辑添加待办事项标记待办事项删除待办事项保存待办事项 总结 在此章节中&#xff0c;我们将学习如何使用JavaScript创建一个简单的网页应用。这将是一个待办事项列表应用&#xff0c;用户可以添加新的待办事项&#xff0c;标记已完成的事项…

使用Python实现银行管理系统

使用Python实现银行管理系统 题目介绍程序演示登录开户查询取款存款转账锁定解锁存盘退出 相关代码开户功能查询功能取款功能存款功能转账功能锁定功能解锁功能存盘功能加载存盘的数据登录/登出功能主程序 完整代码 在本篇博文中&#xff0c;我们将使用Python编写一个简单的银行…

MySQL 8 配置文件详解与最佳实践

MySQL 8 是一款强大的关系型数据库管理系统&#xff0c;通过适当的配置文件设置&#xff0c;可以充分发挥其性能潜力。在这篇博客中&#xff0c;我们将深入探究 MySQL 8 常用的配置文件&#xff0c;并提供一些建议&#xff0c;帮助您优化数据库性能。 配置文件概览 在 MySQL …