很多程序员在上大学的时候都学过如何画流程图, 或根据流程写代码, 或者根据代码写流程图.
来让我们看看如下的流程, 我们用 www.plantuml.com 所提供的工具绘制如下语音交互应答, 所对应的场景是打电话查询帐户余额
- ivr_flow_sample.txt:
@startuml
start
# 播放欢迎音乐
:play_prompt({"clip":"welcome_with_music"});
:isDone = false;
repeat
# 收集用户的帐号
:idValue = collect_dtmf({"clip":"please_input_id"});
:isRight = check_id(idValue);
if (isRight?) then (yes)
#如果帐号正确, 取得帐户余额并读出来
:cash = account_balance(idValue);
:say(cash);
:isDone = true;
else (no)
#如果帐号不正确, 播放错误提示并重新收集帐号
:play_prompt({"clip":"incorrect_inputted_id"});
:play_prompt({"clip":"please_input_id"});
endif
:i = i + 1;
repeat while (i < 3 or isDone?)
:play_prompt({"clip":"good_bye"});
stop
@enduml
- 执行命令
java -jar plantuml.jar ivr_flow_sample.txt
, 得到如下流程图:
我们可以用代码很容易来实现上述流程, 可是银行的呼叫系统有很多流程, 不同的银行也有很多不同之处, 所以一般我们会预先定义一个工作流
image.png
对于刚才的流程我们需要进行一些抽象, 总结起来就是业务流程模型和标记 BPMN(Business Process Model and Notation), 它是一个业务流程模型及其处理的图形化表示.
sampleBPMN 2.0 规范定义了如下概念
- Event 启动与结束事件
- Sequence Flow 顺序流
- Taks 任务
- Gateway 网关
- Subprocess 子流程
- Boundary Event 边界事件
- Intermediate Event 中间事件
- Listener 监听器
事件
event事件可以由外部传入, 也可由异常或者超时来触发
活动
活动就是一个个执行单元
activity网关
网关用户控制流程走向, 也称为执行令牌
gateway
连接
connection工作流 Workflow 的 BPMN 有不少的实现, 例如著名的 Activiti, 在现实生活中大多数场景并不需要这么重的东西.
比如比较简单的流程可以用职责链模式结合命令模式来实现, 这里用到了 commons chain, 参见 命令和职责链模式的快速实现 commons chain
CoR比如
package com.cisco.yafan.demo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.apache.commons.chain.impl.ChainBase;
import org.apache.commons.chain.impl.ContextBase;
import org.junit.Test;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertFalse;
@Slf4j
class IvrCommand implements Command {
private String name;
public IvrCommand(String name) {
this.name = name;
}
@Override
public boolean execute(Context context) throws Exception {
log.info("execute: {} for {}", name, context.get("phoneNumber"));
if("please input your password".equals(name)) {
if("pass".equals(context.get("password"))) {
context.put("balance", 10000);
} else {
log.info("your password is incorrect.");
return true;
}
}
return false;
}
}
class IvrFlow extends ChainBase {
public IvrFlow() {
addCommand(new IvrCommand("welcome"));
addCommand(new IvrCommand("please input account number"));
addCommand(new IvrCommand("please input your password"));
addCommand(new IvrCommand("your account balance is 10000"));
addCommand(new IvrCommand("goodbye"));
}
}
@Slf4j
public class CommonsChainTest {
@Test
public void testIvrFlowCorrectPassword() throws Exception {
Context context = new ContextBase();
context.put("phoneNumber" , "86-123456789");
context.put("password" , "pass");
IvrFlow ivrFlow = new IvrFlow();
ivrFlow.execute(context);
assertTrue(Integer.valueOf("10000").equals(context.get("balance")));
}
@Test
public void testIvrFlowIncorrectPassword() throws Exception {
Context context = new ContextBase();
context.put("phoneNumber" , "86-123456789");
context.put("password" , "abcd");
IvrFlow ivrFlow = new IvrFlow();
ivrFlow.execute(context);
assertFalse(Integer.valueOf("10000").equals(context.get("balance")));
}
}
结果输出如下
#testIvrFlowCorrectPassword
execute: welcome for 86-123456789
execute: please input account number for 86-123456789
execute: please input your password for 86-123456789
execute: your account balance is 10000 for 86-123456789
execute: goodbye for 86-123456789
#testIvrFlowIncorrectPassword
execute: welcome for 86-123456789
execute: please input account number for 86-123456789
execute: please input your password for 86-123456789
your password is incorrect.
很显然,这只能应付简单的流程。
稍加扩展, 就可以描绘更复杂的流程。
就象我们在绘制流程图时,除了顺序结构,就是分支结构和循环结构。
分支结构就是:条件判断与执行体
循环结构也是:条件判断与执行体,只不过这个条件包含了何时终止循环
所以我们可以构造一个链表
每个 command 是一个节点,它有一个属性 nextComands 来存储接下来要执行的结点
map<Condition, Command > nextCommands
而 Condition 类包括 variable, value ,这样就可以描述一个复杂的流程了。
class Condition
{
private String variable;
private Object value ;
public boolean test(Context context ) {
return value.equals(context.get(variable ) ) ;
}
}
网友评论