美文网首页编程笔记
如何定义和实现流程

如何定义和实现流程

作者: 老瓦在霸都 | 来源:发表于2019-03-03 23:02 被阅读27次

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

    来让我们看看如下的流程, 我们用 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, 得到如下流程图:
    IVR_Flow

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


    image.png

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

    sample

    BPMN 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 ) ) ;
    }
    }
    

    相关文章

      网友评论

        本文标题:如何定义和实现流程

        本文链接:https://www.haomeiwen.com/subject/oaceuqtx.html