需求
這一年下來,寫兩次工作流流轉,總結下經驗。
第一次寫的時候,只找到用模版設計模式包裹一下,每個方法都做隔離,但是在具體分支實現的時候,if/else 滿屏分,而且因為要針對不同情況,重復代碼很多,但是if/else的條件又不一樣,搞得我沒辦法用設計模式修改,想過用工廠模式重構。
一是沒時間,二是工廠模式和策略模式基本上都用不來,
首先,工廠模式一定是if else分支較多,并且入參明確、固定。
策略模式也是不同的方法,實現不同的業務,入參明確、固定。
它們兩者都不適合參數多一個少一個的情況,用起來只能說惡心自己。
并且由于設計模式的方法過多,時常debug需要嵌套跳轉好幾輪代碼,就比較惡心。
這一年,閑下來我都會重構部分重復的代碼,比如if else過多用設計模式優化,優化下來的感受是,沒感覺可讀性有多提高,反而感覺代碼可讀性變差了,有些案例的設計模式,很多情況沒考慮到,比如較多重復代碼,直接復用interface default里的方法,給我直觀的體驗是其他人來看這個代碼,不太好理解。
還不如if else來的直接。
反正只要在if else上注釋寫清楚,管什么可讀性。
設計模式做不到事
舉個例子,當有個非常惡心的業務,需要在兩層for循環里寫if else,continue關鍵字是你貼心侍衛,常伴汝身。
你必須用continue它,艸,這個東西用設計模式就不合理,只能復用一些代碼,放到一個方法里面去,什么兩層for循環里,寫個四五百行if else,調試都要好幾天,我不知道要是業務出現變動,這個代碼后面還怎么改。
新奇的思路
再次遇到工作流,吃過一次虧,不能走老路。
我選擇網上沖浪,翻閱資料,最終找到一篇好用例子。
什么都沒說,直接上項目,擦,一用才知道里面有坑。
原案例
- 定義流程節點
首先定義一個抽象類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);}
}
- 定義流程實例
定義一個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);}// 以此類推 ...}}
}
以上,就是今天的內容。