如何定义和实现流程

很多程序员在上大学的时候都学过如何画流程图, 或根据流程写代码, 或者根据代码写流程图.

来让我们看看如下的流程, 我们用 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, 得到如下流程图:
如何定义和实现流程_第1张图片
IVR_Flow

我们可以用代码很容易来实现上述流程, 可是银行的呼叫系统有很多流程, 不同的银行也有很多不同之处, 所以一般我们会预先定义一个工作流


如何定义和实现流程_第2张图片
image.png

对于刚才的流程我们需要进行一些抽象, 总结起来就是业务流程模型和标记 BPMN(Business Process Model and Notation), 它是一个业务流程模型及其处理的图形化表示.

如何定义和实现流程_第3张图片
sample

BPMN 2.0 规范定义了如下概念

  • Event 启动与结束事件
  • Sequence Flow 顺序流
  • Taks 任务
  • Gateway 网关
  • Subprocess 子流程
  • Boundary Event 边界事件
  • Intermediate Event 中间事件
  • Listener 监听器

事件

如何定义和实现流程_第4张图片
event

事件可以由外部传入, 也可由异常或者超时来触发

活动

活动就是一个个执行单元

如何定义和实现流程_第5张图片
activity

网关

网关用户控制流程走向, 也称为执行令牌


如何定义和实现流程_第6张图片
gateway

连接

如何定义和实现流程_第7张图片
connection

工作流 Workflow 的 BPMN 有不少的实现, 例如著名的 Activiti, 在现实生活中大多数场景并不需要这么重的东西.

比如比较简单的流程可以用职责链模式结合命令模式来实现, 这里用到了 commons chain, 参见 命令和职责链模式的快速实现 commons chain

如何定义和实现流程_第8张图片
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 nextCommands

而 Condition 类包括 variable, value ,这样就可以描述一个复杂的流程了。

class Condition
{
private String variable;
private Object value ;

public boolean test(Context context ) {
    return value.equals(context.get(variable ) ) ;
}
}

你可能感兴趣的:(如何定义和实现流程)